From 78fd7b84df16b1a57b79e46a2ab8dcb23bdc52cd Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 5 Jul 2025 12:54:07 +0530 Subject: [PATCH 1/5] feat: truncate long directory names in working set --- src/project/WorkingSetView.js | 56 ++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/src/project/WorkingSetView.js b/src/project/WorkingSetView.js index 5807d52649..15b6faec50 100644 --- a/src/project/WorkingSetView.js +++ b/src/project/WorkingSetView.js @@ -1001,26 +1001,60 @@ define(function (require, exports, module) { }; /** - * Adds full directory names to elements representing passed files in working tree + * adds the directory name to external project files + * when the directory length is more than 3, we show it in this format: `/.../` + * otherwise the full path * @private - * @param {Array.} filesPathList - list of fullPath strings + * @param {Array.} externalProjectFiles - the list of the external project files */ - WorkingSetView.prototype._addFullDirectoryNamesToWorkingTreeFiles = function (filesPathList) { - // filesList must have at least two files in it for this to make sense - if (!filesPathList.length) { + WorkingSetView.prototype._addDirectoryNameToExternalProjectFiles = function (externalProjectFiles) { + if(!externalProjectFiles.length) { return; } - // Go through open files and add directories to appropriate entries this.$openFilesContainer.find("ul > li").each(function () { const $li = $(this); let filePath = $li.data(_FILE_KEY).fullPath; - const io = filesPathList.indexOf(filePath); + const io = externalProjectFiles.indexOf(filePath); if (io !== -1) { let dirPath = path.dirname(filePath); - dirPath = Phoenix.app.getDisplayPath(dirPath); - const $dir = $(``) - .html(" — " + dirPath); + // this will be displayed on hover + const displayPath = Phoenix.app.getDisplayPath(dirPath); + + let separator; + if (Phoenix.isNativeApp) { + separator = brackets.platform === "win" ? "\\" : "/"; + } else { + separator = "/"; + } + + let dirSplit = displayPath.split(separator); + + let truncatedPath = displayPath; // truncatedPath value will be shown in the UI + if (dirSplit.length > 3) { + let rootDirName; + + if (Phoenix.isNativeApp) { + // Desktop app paths + // dirSplit[0] maybe empty sometimes for absolute paths on Linux/Mac eg: "/root/fs/path/to/file" + rootDirName = dirSplit[0] ? dirSplit[0] : dirSplit[1]; + } else { + // Browser paths - handle different formats + if (displayPath.startsWith('/')) { + // Internal/default projects: /fs/path/to/file + // dirSplit[0] will be empty due to leading '/' + rootDirName = dirSplit[1] || dirSplit[0]; + } else { + // User-opened system folders: path/to/file (no leading '/') + rootDirName = dirSplit[0]; + } + } + + truncatedPath = rootDirName + separator + "\u2026" + separator + dirSplit[dirSplit.length - 1]; + } + + const $dir = $(``) + .html(" — " + truncatedPath); $li.children("a").append($dir); } }); @@ -1084,7 +1118,7 @@ define(function (require, exports, module) { } }); - self._addFullDirectoryNamesToWorkingTreeFiles(externalProjectFiles); + self._addDirectoryNameToExternalProjectFiles(externalProjectFiles); // Go through the map and solve the arrays with length over 1. Ignore the rest. _.forEach(map, function (value) { From a444902a5780674c22997cda69bbccaa9d4a09ef Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 5 Jul 2025 21:46:37 +0530 Subject: [PATCH 2/5] feat: integ tests for external file paths with ellipsis --- test/spec/WorkingSetView-integ-test.js | 35 +++++++++++++++----------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/test/spec/WorkingSetView-integ-test.js b/test/spec/WorkingSetView-integ-test.js index 564daa0a9f..93f3f5ada8 100644 --- a/test/spec/WorkingSetView-integ-test.js +++ b/test/spec/WorkingSetView-integ-test.js @@ -19,7 +19,7 @@ * */ -/*global describe, it, expect, beforeEach, afterEach, awaitsFor, awaitsForDone, beforeAll, afterAll, awaits */ +/*global describe, it, expect, beforeEach, afterEach, awaitsFor, awaitsForDone, beforeAll, afterAll, awaits, path */ define(function (require, exports, module) { @@ -226,28 +226,33 @@ define(function (require, exports, module) { expect($list.find(".directory").length).toBe(0); }); - it("should show full path next to the file name when file is outside current project", async function () { - // Count currently opened files - var workingSetListItemCountBeforeTest = workingSetListItemCount; + it("should show ellipsis on external project files", async function () { + // empty the working set + await testWindow.closeAllFiles(); + workingSetListItemCount = 0; - // First we need to open another file await openAndMakeDirty(externalProjectTestPath + "/test.js"); - // Wait for file to be added to the working set - await awaitsFor(function () { return workingSetListItemCount === workingSetListItemCountBeforeTest + 1; }); + // wait for the file to add to the working set + await awaitsFor(function () { return workingSetListItemCount === 1; }, "Open file count to be 1"); - // Two files with the same name file_one.js should be now opened + // get the directory path var $list = testWindow.$(".open-files-container > ul"); - const fullPathSpan = $list.find(".directory"); - expect(fullPathSpan.length).toBe(1); - expect(fullPathSpan[0].innerHTML.includes(Phoenix.app.getDisplayPath(externalProjectTestPath))).toBe(true); + const directorySpan = $list.find(".directory"); + expect(directorySpan.length).toBe(1); - // Now close last opened file to hide the directories again + // get the text from the directory path + const directoryText = directorySpan[0].innerHTML; + + // check if the directory path has ellipsis + expect(directoryText.includes("\u2026")).toBe(true); + + // the title should contain the full path + expect(directorySpan.attr('title')).toBe(Phoenix.app.getDisplayPath(path.dirname(externalProjectTestPath + "/test.js"))); + + // Clean up DocumentManager.getCurrentDocument()._markClean(); // so we can close without a save dialog await awaitsForDone(CommandManager.execute(Commands.FILE_CLOSE), "timeout on FILE_CLOSE", 1000); - - // there should be no more directories shown - expect($list.find(".directory").length).toBe(0); }); it("should show different directory names, when two files of the same name are opened, located in folders with same name", async function () { From 363c0be336075b0740c81905708ccfa896773a50 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 6 Jul 2025 17:40:47 +0530 Subject: [PATCH 3/5] fix: path inconsistencies and tests --- src/project/WorkingSetView.js | 61 +++++++++++++++----------- test/spec/WorkingSetView-integ-test.js | 25 +++++++++-- 2 files changed, 57 insertions(+), 29 deletions(-) diff --git a/src/project/WorkingSetView.js b/src/project/WorkingSetView.js index 15b6faec50..d042a09d15 100644 --- a/src/project/WorkingSetView.js +++ b/src/project/WorkingSetView.js @@ -1017,40 +1017,49 @@ define(function (require, exports, module) { let filePath = $li.data(_FILE_KEY).fullPath; const io = externalProjectFiles.indexOf(filePath); if (io !== -1) { + const displayPath = Phoenix.app.getDisplayPath(filePath); let dirPath = path.dirname(filePath); - // this will be displayed on hover - const displayPath = Phoenix.app.getDisplayPath(dirPath); - - let separator; - if (Phoenix.isNativeApp) { - separator = brackets.platform === "win" ? "\\" : "/"; + // this will be displayed on hover GetDisplayPath returns + // windows: C://some/path , linux/mac: /some/path + // a relative path of the form `folder/file.txt` (no-leading slash) for fs access paths- /mnt/paths + // or virtual path if its a browser indexed db - backed path like /fs/virtual/path + const displayDirPath = Phoenix.app.getDisplayPath(dirPath); + + let sep; + if (Phoenix.isNativeApp && brackets.platform === "win") { + sep = "\\"; } else { - separator = "/"; + sep = "/"; } - let dirSplit = displayPath.split(separator); + // Split the path and filter out empty segments + let dirSplit = displayDirPath.split(sep).filter(segment => segment !== ''); - let truncatedPath = displayPath; // truncatedPath value will be shown in the UI + let truncatedPath = displayDirPath; // truncatedPath value will be shown in the UI if (dirSplit.length > 3) { - let rootDirName; - - if (Phoenix.isNativeApp) { - // Desktop app paths - // dirSplit[0] maybe empty sometimes for absolute paths on Linux/Mac eg: "/root/fs/path/to/file" - rootDirName = dirSplit[0] ? dirSplit[0] : dirSplit[1]; + const rootDirName = dirSplit[0]; + const secondLastSegment = dirSplit[dirSplit.length - 2]; + const fileName = dirSplit[dirSplit.length - 1]; + + if (Phoenix.isNativeApp && brackets.platform === "win") { + // Eg: C:\\long\path\to\file.txt - > C:\\...\to\file.txt + truncatedPath = `${rootDirName}:${sep}${sep}\u2026${sep}${secondLastSegment}${sep}${fileName}`; + } else if (Phoenix.isNativeApp) { + // an absolute path of the form /abs/path/to/file in linux/mac desktop + // Eg: /application/path/to/file.txt - > /application/.../to/file.txt + truncatedPath = `${sep}${rootDirName}${sep}\u2026${sep}${secondLastSegment}${sep}${fileName}`; + } else if (!Phoenix.isNativeApp && !displayDirPath.startsWith('/')){ + // browser fs access path: `folder/file.txt` (no-leading slash) fs access paths- /mnt/paths + // Eg: opened/folder/path/to/file.txt - > opened/.../to/file.txt (no-leading slash) + truncatedPath = `${rootDirName}${sep}\u2026${sep}${secondLastSegment}${sep}${fileName}`; } else { - // Browser paths - handle different formats - if (displayPath.startsWith('/')) { - // Internal/default projects: /fs/path/to/file - // dirSplit[0] will be empty due to leading '/' - rootDirName = dirSplit[1] || dirSplit[0]; - } else { - // User-opened system folders: path/to/file (no leading '/') - rootDirName = dirSplit[0]; - } + //this is an internal indexed db backed virtual path. This can only happen if we allow virtual + // project locations from one project to be opened in another. So just print the trim path + // path in this case. In future, when we add this support, the get display path fn should be + // modified to give somethings like what we do for fs access path. + // Eg: /application/path/to/file.txt - > /application/.../to/file.txt + truncatedPath = `${sep}${rootDirName}${sep}\u2026${sep}${secondLastSegment}${sep}${fileName}`; } - - truncatedPath = rootDirName + separator + "\u2026" + separator + dirSplit[dirSplit.length - 1]; } const $dir = $(``) diff --git a/test/spec/WorkingSetView-integ-test.js b/test/spec/WorkingSetView-integ-test.js index 93f3f5ada8..76c45360cd 100644 --- a/test/spec/WorkingSetView-integ-test.js +++ b/test/spec/WorkingSetView-integ-test.js @@ -37,7 +37,7 @@ define(function (require, exports, module) { describe("mainview:WorkingSetView", function () { var testPath = SpecRunnerUtils.getTestPath("/spec/WorkingSetView-test-files"), - externalProjectTestPath = SpecRunnerUtils.getTestPath("/spec/MainViewManager-test-files"), + externalProjectTestPath = SpecRunnerUtils.getTestPath("/spec/MainViewFactory-test-files/css"), testWindow, workingSetListItemCount; @@ -230,8 +230,19 @@ define(function (require, exports, module) { // empty the working set await testWindow.closeAllFiles(); workingSetListItemCount = 0; + const virtualPath = externalProjectTestPath + "/tablet.css"; + const hoverFullPath = Phoenix.app.getDisplayPath(virtualPath); + + let sep; + if (Phoenix.isNativeApp && brackets.platform === "win") { + sep = "\\"; + } else { + sep = "/"; + } + let dirSplit = Phoenix.app.getDisplayPath(virtualPath).split(sep).filter(segment => segment !== ''); + const root = dirSplit[0]; - await openAndMakeDirty(externalProjectTestPath + "/test.js"); + await openAndMakeDirty(virtualPath); // wait for the file to add to the working set await awaitsFor(function () { return workingSetListItemCount === 1; }, "Open file count to be 1"); @@ -246,9 +257,17 @@ define(function (require, exports, module) { // check if the directory path has ellipsis expect(directoryText.includes("\u2026")).toBe(true); + if (!Phoenix.isNativeApp) { + expect(directoryText).toBe(' — /test/…/MainViewFactory-test-files/css'); + } else if (brackets.platform === "linux" || brackets.platform === "mac") { + expect(directoryText).toBe(` — /${root}/…/MainViewFactory-test-files/css`); + } else { + // windows + expect(directoryText).toBe(` — ${root}:\\\\…\\MainViewFactory-test-files\\css`); + } // the title should contain the full path - expect(directorySpan.attr('title')).toBe(Phoenix.app.getDisplayPath(path.dirname(externalProjectTestPath + "/test.js"))); + expect(directorySpan.attr('title')).toBe(hoverFullPath); // Clean up DocumentManager.getCurrentDocument()._markClean(); // so we can close without a save dialog From f65e417690cbf27d2dccda29c0e67029988c85b9 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 6 Jul 2025 17:53:39 +0530 Subject: [PATCH 4/5] refactor: var names --- src-node/package-lock.json | 4 ++-- src/project/WorkingSetView.js | 20 ++++++++++---------- test/spec/WorkingSetView-integ-test.js | 4 +++- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src-node/package-lock.json b/src-node/package-lock.json index 6183efc95f..2a5b79a17a 100644 --- a/src-node/package-lock.json +++ b/src-node/package-lock.json @@ -1,12 +1,12 @@ { "name": "@phcode/node-core", - "version": "4.1.1-0", + "version": "4.1.2-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@phcode/node-core", - "version": "4.1.1-0", + "version": "4.1.2-0", "license": "GNU-AGPL3.0", "dependencies": { "@phcode/fs": "^3.0.1", diff --git a/src/project/WorkingSetView.js b/src/project/WorkingSetView.js index d042a09d15..7e98817a33 100644 --- a/src/project/WorkingSetView.js +++ b/src/project/WorkingSetView.js @@ -1039,26 +1039,26 @@ define(function (require, exports, module) { if (dirSplit.length > 3) { const rootDirName = dirSplit[0]; const secondLastSegment = dirSplit[dirSplit.length - 2]; - const fileName = dirSplit[dirSplit.length - 1]; + const lastSeg = dirSplit[dirSplit.length - 1]; if (Phoenix.isNativeApp && brackets.platform === "win") { - // Eg: C:\\long\path\to\file.txt - > C:\\...\to\file.txt - truncatedPath = `${rootDirName}:${sep}${sep}\u2026${sep}${secondLastSegment}${sep}${fileName}`; + // Eg: C:\\long\path\to\fileDir - > C:\\...\to\fileDir + truncatedPath = `${rootDirName}:${sep}${sep}\u2026${sep}${secondLastSegment}${sep}${lastSeg}`; } else if (Phoenix.isNativeApp) { // an absolute path of the form /abs/path/to/file in linux/mac desktop - // Eg: /application/path/to/file.txt - > /application/.../to/file.txt - truncatedPath = `${sep}${rootDirName}${sep}\u2026${sep}${secondLastSegment}${sep}${fileName}`; + // Eg: /application/path/to/fileDir - > /application/.../to/fileDir + truncatedPath = `${sep}${rootDirName}${sep}\u2026${sep}${secondLastSegment}${sep}${lastSeg}`; } else if (!Phoenix.isNativeApp && !displayDirPath.startsWith('/')){ - // browser fs access path: `folder/file.txt` (no-leading slash) fs access paths- /mnt/paths - // Eg: opened/folder/path/to/file.txt - > opened/.../to/file.txt (no-leading slash) - truncatedPath = `${rootDirName}${sep}\u2026${sep}${secondLastSegment}${sep}${fileName}`; + // browser fs access path: `folder/fileDir` (no-leading slash) fs access paths- /mnt/paths + // Eg: opened/folder/path/to/fileDir - > opened/.../to/fileDir (no-leading slash) + truncatedPath = `${rootDirName}${sep}\u2026${sep}${secondLastSegment}${sep}${lastSeg}`; } else { //this is an internal indexed db backed virtual path. This can only happen if we allow virtual // project locations from one project to be opened in another. So just print the trim path // path in this case. In future, when we add this support, the get display path fn should be // modified to give somethings like what we do for fs access path. - // Eg: /application/path/to/file.txt - > /application/.../to/file.txt - truncatedPath = `${sep}${rootDirName}${sep}\u2026${sep}${secondLastSegment}${sep}${fileName}`; + // Eg: /application/path/to/fileDir - > /application/.../to/fileDir + truncatedPath = `${sep}${rootDirName}${sep}\u2026${sep}${secondLastSegment}${sep}${lastSeg}`; } } diff --git a/test/spec/WorkingSetView-integ-test.js b/test/spec/WorkingSetView-integ-test.js index 76c45360cd..2c92f54dac 100644 --- a/test/spec/WorkingSetView-integ-test.js +++ b/test/spec/WorkingSetView-integ-test.js @@ -201,7 +201,9 @@ define(function (require, exports, module) { await awaits(ProjectManager._RENDER_DEBOUNCE_TIME + 50); - expect($("#project-files-container ul input").val()).toBe(fileName); + await awaitsFor(function () { + return $("#project-files-container ul input").val() === fileName; + }); }); it("should show a directory name next to the file name when two files with same names are opened", async function () { From e2a6c6fb52d21e009fe3a644d092b63021ee0e15 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 6 Jul 2025 18:08:07 +0530 Subject: [PATCH 5/5] fix: extra : in windows path --- src/project/WorkingSetView.js | 4 ++-- test/spec/WorkingSetView-integ-test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/project/WorkingSetView.js b/src/project/WorkingSetView.js index 7e98817a33..bfa4cb7b95 100644 --- a/src/project/WorkingSetView.js +++ b/src/project/WorkingSetView.js @@ -1042,8 +1042,8 @@ define(function (require, exports, module) { const lastSeg = dirSplit[dirSplit.length - 1]; if (Phoenix.isNativeApp && brackets.platform === "win") { - // Eg: C:\\long\path\to\fileDir - > C:\\...\to\fileDir - truncatedPath = `${rootDirName}:${sep}${sep}\u2026${sep}${secondLastSegment}${sep}${lastSeg}`; + // Eg: C:\long\path\to\fileDir - > C:\...\to\fileDir -- [rootDirName = c: here] + truncatedPath = `${rootDirName}${sep}\u2026${sep}${secondLastSegment}${sep}${lastSeg}`; } else if (Phoenix.isNativeApp) { // an absolute path of the form /abs/path/to/file in linux/mac desktop // Eg: /application/path/to/fileDir - > /application/.../to/fileDir diff --git a/test/spec/WorkingSetView-integ-test.js b/test/spec/WorkingSetView-integ-test.js index 2c92f54dac..de04c7f32b 100644 --- a/test/spec/WorkingSetView-integ-test.js +++ b/test/spec/WorkingSetView-integ-test.js @@ -265,7 +265,7 @@ define(function (require, exports, module) { expect(directoryText).toBe(` — /${root}/…/MainViewFactory-test-files/css`); } else { // windows - expect(directoryText).toBe(` — ${root}:\\\\…\\MainViewFactory-test-files\\css`); + expect(directoryText).toBe(` — ${root}\\…\\MainViewFactory-test-files\\css`); } // the title should contain the full path