From cc8f1220d90b40168319843b937d388cb837d0ff Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 15 May 2025 00:59:07 +0530 Subject: [PATCH 01/13] fix: always reserve space for dirty icon to prevent tabs shift --- package-lock.json | 4 ++-- src-node/package-lock.json | 4 ++-- src/styles/Extn-TabBar.less | 17 +++++++++++------ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index b9a6b8e218..778cf7d205 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "phoenix", - "version": "4.1.0-0", + "version": "4.1.1-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "phoenix", - "version": "4.1.0-0", + "version": "4.1.1-0", "dependencies": { "@bugsnag/js": "^7.18.0", "@floating-ui/dom": "^0.5.4", diff --git a/src-node/package-lock.json b/src-node/package-lock.json index 6177376876..6183efc95f 100644 --- a/src-node/package-lock.json +++ b/src-node/package-lock.json @@ -1,12 +1,12 @@ { "name": "@phcode/node-core", - "version": "4.1.0-0", + "version": "4.1.1-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@phcode/node-core", - "version": "4.1.0-0", + "version": "4.1.1-0", "license": "GNU-AGPL3.0", "dependencies": { "@phcode/fs": "^3.0.1", diff --git a/src/styles/Extn-TabBar.less b/src/styles/Extn-TabBar.less index fb4288f541..5400a58ef0 100644 --- a/src/styles/Extn-TabBar.less +++ b/src/styles/Extn-TabBar.less @@ -80,7 +80,7 @@ .tab { display: inline-flex; align-items: center; - padding: 0 0.5rem 0 0.85rem; + padding: 0 0.5rem 0 0.6rem; height: 100%; background-color: #f1f1f1; border-right: 1px solid rgba(0, 0, 0, 0.05); @@ -108,6 +108,7 @@ display: flex; align-items: center; margin-bottom: -2px; + margin-left: 0.7rem; } .tab-name { @@ -191,21 +192,25 @@ background-color: #666; } -.tab.dirty::before { +.tab::before { content: "•"; - color: #888; + color: transparent; font-size: 1.6rem; position: absolute; - left: 0.75rem; + left: 0.4rem; top: 0.25rem; } +.tab.dirty::before { + color: #888; +} + .dark .tab.dirty::before { color: #8D8D8E; } .tab.dirty .tab-icon { - margin-left: 0.8rem; + margin-left: 0.7rem; } .tab-close { @@ -408,4 +413,4 @@ .dropdown-tab-item.placeholder-item .tab-name-container, .tab-name-container.placeholder-name { font-style: italic; -} \ No newline at end of file +} From e5557d52660dddf710de12b8433dfae919845a3d Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 15 May 2025 01:31:01 +0530 Subject: [PATCH 02/13] feat: add rename and delete option in tab --- .../TabBar/more-options.js | 46 +++++++++++++++++++ src/nls/root/strings.js | 2 + 2 files changed, 48 insertions(+) diff --git a/src/extensionsIntegrated/TabBar/more-options.js b/src/extensionsIntegrated/TabBar/more-options.js index f734741f44..1156d3d4f2 100644 --- a/src/extensionsIntegrated/TabBar/more-options.js +++ b/src/extensionsIntegrated/TabBar/more-options.js @@ -40,6 +40,9 @@ define(function (require, exports, module) { Strings.CLOSE_ALL_TABS, Strings.CLOSE_UNMODIFIED_TABS, "---", + Strings.RENAME_TAB_FILE, + Strings.DELETE_TAB_FILE, + "---", Strings.REOPEN_CLOSED_FILE ]; @@ -219,6 +222,43 @@ define(function (require, exports, module) { } + /** + * "RENAME FILE" + * This function handles the renaming of the file that was right-clicked + * + * @param {String} filePath - path of the file to rename + */ + function handleFileRename(filePath) { + if (filePath) { + // First ensure the sidebar is visible so users can see the rename action + CommandManager.execute(Commands.SHOW_SIDEBAR); + + // Get the file object using FileSystem + const fileObj = FileSystem.getFileForPath(filePath); + + // Execute the rename command with the file object + CommandManager.execute(Commands.FILE_RENAME, {file: fileObj}); + } + } + + + /** + * "DELETE FILE" + * This function handles the deletion of the file that was right-clicked + * + * @param {String} filePath - path of the file to delete + */ + function handleFileDelete(filePath) { + if (filePath) { + // Get the file object using FileSystem + const fileObj = FileSystem.getFileForPath(filePath); + + // Execute the delete command with the file object + CommandManager.execute(Commands.FILE_DELETE, {file: fileObj}); + } + } + + /** * This function is called when a tab is right-clicked * This will show the more options context menu @@ -279,6 +319,12 @@ define(function (require, exports, module) { case Strings.CLOSE_UNMODIFIED_TABS: handleCloseUnmodifiedTabs(paneId); break; + case Strings.RENAME_TAB_FILE: + handleFileRename(filePath); + break; + case Strings.DELETE_TAB_FILE: + handleFileDelete(filePath); + break; case Strings.REOPEN_CLOSED_FILE: reopenClosedFile(); break; diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 7b996f7da4..6051603272 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -433,6 +433,8 @@ define({ "CLOSE_ALL_TABS": "Close All Tabs", "CLOSE_UNMODIFIED_TABS": "Close Unmodified Tabs", "REOPEN_CLOSED_FILE": "Reopen Closed File", + "RENAME_TAB_FILE": "Rename File", + "DELETE_TAB_FILE": "Delete File", // CodeInspection: errors/warnings "ERRORS_NO_FILE": "No File Open", From 9e32491f586a38685188928c669d9ce6c797d37e Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 15 May 2025 01:47:08 +0530 Subject: [PATCH 03/13] fix: scrollbar appearing in tab context menu --- src/extensionsIntegrated/TabBar/more-options.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/extensionsIntegrated/TabBar/more-options.js b/src/extensionsIntegrated/TabBar/more-options.js index 1156d3d4f2..0184969d10 100644 --- a/src/extensionsIntegrated/TabBar/more-options.js +++ b/src/extensionsIntegrated/TabBar/more-options.js @@ -282,8 +282,13 @@ define(function (require, exports, module) { zIndex: 1000 }); + // Add a custom class to override the max-height, not sure why a scroll bar was coming but this did the trick + dropdown.dropdownExtraClasses = "tabbar-context-menu"; + dropdown.showDropdown(); + $(".tabbar-context-menu").css("max-height", "200px"); + // handle the option selection dropdown.on("select", function (e, item) { _handleSelection(item, filePath, paneId); From 8ebecef6c1ed1a38e0bd37524c2c2f6db46006e2 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 15 May 2025 01:48:39 +0530 Subject: [PATCH 04/13] fix: remove unused code --- src/extensionsIntegrated/TabBar/more-options.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/extensionsIntegrated/TabBar/more-options.js b/src/extensionsIntegrated/TabBar/more-options.js index 0184969d10..a9bd4547b0 100644 --- a/src/extensionsIntegrated/TabBar/more-options.js +++ b/src/extensionsIntegrated/TabBar/more-options.js @@ -68,18 +68,6 @@ define(function (require, exports, module) { } - /** - * "CLOSE ACTIVE TAB" - * this closes the currently active tab - * doesn't matter if the context menu is opened from this tab or some other tab - */ - function handleCloseActiveTab() { - // This simply executes the FILE_CLOSE command without parameters - // which will close the currently active file - CommandManager.execute(Commands.FILE_CLOSE); - } - - /** * "CLOSE ALL TABS" * This will close all tabs in the specified pane From 1ffe5427206790dc63e915b4293affa2300d37ec Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 15 May 2025 02:14:56 +0530 Subject: [PATCH 05/13] feat: add show in file tree option in the tab context menu --- .../TabBar/more-options.js | 68 +++++++++---------- src/nls/root/strings.js | 1 + 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/extensionsIntegrated/TabBar/more-options.js b/src/extensionsIntegrated/TabBar/more-options.js index a9bd4547b0..f69cd847d6 100644 --- a/src/extensionsIntegrated/TabBar/more-options.js +++ b/src/extensionsIntegrated/TabBar/more-options.js @@ -42,11 +42,11 @@ define(function (require, exports, module) { "---", Strings.RENAME_TAB_FILE, Strings.DELETE_TAB_FILE, + Strings.SHOW_IN_FILE_TREE, "---", Strings.REOPEN_CLOSED_FILE ]; - /** * "CLOSE TAB" * this function handles the closing of the tab that was right-clicked @@ -60,14 +60,10 @@ define(function (require, exports, module) { const fileObj = FileSystem.getFileForPath(filePath); // Execute close command with file object and pane ID - CommandManager.execute( - Commands.FILE_CLOSE, - { file: fileObj, paneId: paneId } - ); + CommandManager.execute(Commands.FILE_CLOSE, { file: fileObj, paneId: paneId }); } } - /** * "CLOSE ALL TABS" * This will close all tabs in the specified pane @@ -88,14 +84,10 @@ define(function (require, exports, module) { // close each file in the pane, start from the rightmost [to avoid index shifts] for (let i = workingSet.length - 1; i >= 0; i--) { const fileObj = FileSystem.getFileForPath(workingSet[i].path); - CommandManager.execute( - Commands.FILE_CLOSE, - { file: fileObj, paneId: paneId } - ); + CommandManager.execute(Commands.FILE_CLOSE, { file: fileObj, paneId: paneId }); } } - /** * "CLOSE UNMODIFIED TABS" * This will close all tabs that are not modified in the specified pane @@ -114,19 +106,15 @@ define(function (require, exports, module) { } // get all those entries that are not dirty - const unmodifiedEntries = workingSet.filter(entry => !entry.isDirty); + const unmodifiedEntries = workingSet.filter((entry) => !entry.isDirty); // close each unmodified file in the pane for (let i = unmodifiedEntries.length - 1; i >= 0; i--) { const fileObj = FileSystem.getFileForPath(unmodifiedEntries[i].path); - CommandManager.execute( - Commands.FILE_CLOSE, - { file: fileObj, paneId: paneId } - ); + CommandManager.execute(Commands.FILE_CLOSE, { file: fileObj, paneId: paneId }); } } - /** * "CLOSE TABS TO THE LEFT" * This function is responsible for closing all tabs to the left of the right-clicked tab @@ -146,24 +134,21 @@ define(function (require, exports, module) { } // find the index of the current file in the working set - const currentIndex = workingSet.findIndex(entry => entry.path === filePath); + const currentIndex = workingSet.findIndex((entry) => entry.path === filePath); - if (currentIndex > 0) { // we only proceed if there are tabs to the left + if (currentIndex > 0) { + // we only proceed if there are tabs to the left // get all files to the left of the current file const filesToClose = workingSet.slice(0, currentIndex); // Close each file, starting from the rightmost [to avoid index shifts] for (let i = filesToClose.length - 1; i >= 0; i--) { const fileObj = FileSystem.getFileForPath(filesToClose[i].path); - CommandManager.execute( - Commands.FILE_CLOSE, - { file: fileObj, paneId: paneId } - ); + CommandManager.execute(Commands.FILE_CLOSE, { file: fileObj, paneId: paneId }); } } } - /** * "CLOSE TABS TO THE RIGHT" * This function is responsible for closing all tabs to the right of the right-clicked tab @@ -183,7 +168,7 @@ define(function (require, exports, module) { } // get the index of the current file in the working set - const currentIndex = workingSet.findIndex(entry => entry.path === filePath); + const currentIndex = workingSet.findIndex((entry) => entry.path === filePath); // only proceed if there are tabs to the right if (currentIndex !== -1 && currentIndex < workingSet.length - 1) { // get all files to the right of the current file @@ -191,15 +176,11 @@ define(function (require, exports, module) { for (let i = filesToClose.length - 1; i >= 0; i--) { const fileObj = FileSystem.getFileForPath(filesToClose[i].path); - CommandManager.execute( - Commands.FILE_CLOSE, - { file: fileObj, paneId: paneId } - ); + CommandManager.execute(Commands.FILE_CLOSE, { file: fileObj, paneId: paneId }); } } } - /** * "REOPEN CLOSED FILE" * This just calls the reopen closed file command. everthing else is handled there @@ -209,7 +190,6 @@ define(function (require, exports, module) { CommandManager.execute(Commands.FILE_REOPEN_CLOSED); } - /** * "RENAME FILE" * This function handles the renaming of the file that was right-clicked @@ -225,11 +205,10 @@ define(function (require, exports, module) { const fileObj = FileSystem.getFileForPath(filePath); // Execute the rename command with the file object - CommandManager.execute(Commands.FILE_RENAME, {file: fileObj}); + CommandManager.execute(Commands.FILE_RENAME, { file: fileObj }); } } - /** * "DELETE FILE" * This function handles the deletion of the file that was right-clicked @@ -242,10 +221,28 @@ define(function (require, exports, module) { const fileObj = FileSystem.getFileForPath(filePath); // Execute the delete command with the file object - CommandManager.execute(Commands.FILE_DELETE, {file: fileObj}); + CommandManager.execute(Commands.FILE_DELETE, { file: fileObj }); } } + /** + * "SHOW IN FILE TREE" + * This function handles showing the file in the file tree + * + * @param {String} filePath - path of the file to show in file tree + */ + function handleShowInFileTree(filePath) { + if (filePath) { + // First ensure the sidebar is visible so users can see the file in the tree + CommandManager.execute(Commands.SHOW_SIDEBAR); + + // Get the file object using FileSystem + const fileObj = FileSystem.getFileForPath(filePath); + + // Execute the show in tree command with the file object + CommandManager.execute(Commands.NAVIGATE_SHOW_IN_FILE_TREE, { file: fileObj }); + } + } /** * This function is called when a tab is right-clicked @@ -318,6 +315,9 @@ define(function (require, exports, module) { case Strings.DELETE_TAB_FILE: handleFileDelete(filePath); break; + case Strings.SHOW_IN_FILE_TREE: + handleShowInFileTree(filePath); + break; case Strings.REOPEN_CLOSED_FILE: reopenClosedFile(); break; diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 6051603272..7228147d6e 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -435,6 +435,7 @@ define({ "REOPEN_CLOSED_FILE": "Reopen Closed File", "RENAME_TAB_FILE": "Rename File", "DELETE_TAB_FILE": "Delete File", + "SHOW_IN_FILE_TREE": "Show in File Tree", // CodeInspection: errors/warnings "ERRORS_NO_FILE": "No File Open", From 0c55a560d858f4f7a925aa8408b00e8bf0ccb202 Mon Sep 17 00:00:00 2001 From: Pluto Date: Thu, 15 May 2025 14:46:22 +0530 Subject: [PATCH 06/13] fix: scroll bar appearing in commit dropdown --- src/extensionsIntegrated/TabBar/more-options.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensionsIntegrated/TabBar/more-options.js b/src/extensionsIntegrated/TabBar/more-options.js index f69cd847d6..8523b30b28 100644 --- a/src/extensionsIntegrated/TabBar/more-options.js +++ b/src/extensionsIntegrated/TabBar/more-options.js @@ -272,7 +272,7 @@ define(function (require, exports, module) { dropdown.showDropdown(); - $(".tabbar-context-menu").css("max-height", "200px"); + $(".tabbar-context-menu").css("max-height", "300px"); // handle the option selection dropdown.on("select", function (e, item) { From 8d68fa66b7f2884a8a47844e22f0a3bc5e7c44cc Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 17 May 2025 14:51:36 +0530 Subject: [PATCH 07/13] fix: drag-drop bugs that was preventing smooth scroll --- src/extensionsIntegrated/TabBar/drag-drop.js | 247 ++++++++++++++----- src/styles/Extn-TabBar.less | 22 +- 2 files changed, 204 insertions(+), 65 deletions(-) diff --git a/src/extensionsIntegrated/TabBar/drag-drop.js b/src/extensionsIntegrated/TabBar/drag-drop.js index 7ece013fe9..ef0d912653 100644 --- a/src/extensionsIntegrated/TabBar/drag-drop.js +++ b/src/extensionsIntegrated/TabBar/drag-drop.js @@ -18,7 +18,6 @@ * */ - /* This file houses the functionality for dragging and dropping tabs */ /* eslint-disable no-invalid-this */ define(function (require, exports, module) { @@ -40,7 +39,6 @@ define(function (require, exports, module) { let scrollInterval = null; let dragSourcePane = null; - /** * Initialize drag and drop functionality for tab bars * This is called from `main.js` @@ -59,14 +57,13 @@ define(function (require, exports, module) { // Create drag indicator element if it doesn't exist if (!dragIndicator) { dragIndicator = $('
'); - $('body').append(dragIndicator); + $("body").append(dragIndicator); } // add initialization for empty panes initEmptyPaneDropTargets(); } - /** * Setup drag and drop for a specific tab bar * Makes tabs draggable and adds all the necessary event listeners @@ -93,7 +90,6 @@ define(function (require, exports, module) { $tabs.on("dragend", handleDragEnd); } - /** * Setup container-level drag events * This enables dropping tabs in empty spaces and auto-scrolling @@ -103,6 +99,54 @@ define(function (require, exports, module) { */ function setupContainerDrag(containerSelector) { const $container = $(containerSelector); + let lastKnownMousePosition = { x: 0 }; + const boundaryTolerance = 50; // px tolerance outside the container that still allows dropping + + // create a larger drop zone around the container + // this is done to make sure that even if the tab is not exactly over the tab bar, we still allow drag-drop + const createOuterDropZone = () => { + if (draggedTab && !$("#tab-drag-extended-zone").length) { + // an invisible larger zone around the container that can still receive drops + const containerRect = $container[0].getBoundingClientRect(); + const $outerZone = $('
').css({ + position: "fixed", + top: containerRect.top - boundaryTolerance, + left: containerRect.left - boundaryTolerance, + width: containerRect.width + boundaryTolerance * 2, + height: containerRect.height + boundaryTolerance * 2, + zIndex: 9999, + pointerEvents: "all" + }); + + $("body").append($outerZone); + + $outerZone.on("dragover", function (e) { + e.preventDefault(); + e.stopPropagation(); + lastKnownMousePosition.x = e.originalEvent.clientX; + + autoScrollContainer($container[0], lastKnownMousePosition.x); + + updateDragIndicatorFromOuterZone($container, lastKnownMousePosition.x); + + return false; + }); + + $outerZone.on("drop", function (e) { + e.preventDefault(); + e.stopPropagation(); + + // to handle drop the same way as if it happened in the container + handleOuterZoneDrop($container, lastKnownMousePosition.x); + + return false; + }); + } + }; + + const removeOuterDropZone = () => { + $("#tab-drag-extended-zone").remove(); + }; // When dragging over the container but not directly over a tab element $container.on("dragover", function (e) { @@ -110,6 +154,8 @@ define(function (require, exports, module) { e.preventDefault(); } + lastKnownMousePosition.x = e.originalEvent.clientX; + // Clear any existing scroll interval if (scrollInterval) { clearInterval(scrollInterval); @@ -120,30 +166,35 @@ define(function (require, exports, module) { // Set up interval for continuous scrolling while dragging near the edge scrollInterval = setInterval(() => { - if (draggedTab) { // Only continue scrolling if still dragging - autoScrollContainer(this, e.originalEvent.clientX); + if (draggedTab) { + // Only continue scrolling if still dragging + autoScrollContainer(this, lastKnownMousePosition.x); } else { clearInterval(scrollInterval); scrollInterval = null; } }, 16); // this is almost about 60fps - // if the target is not a tab, update the drag indicator using the container bounds - if ($(e.target).closest('.tab').length === 0) { + if ($(e.target).closest(".tab").length === 0) { const containerRect = this.getBoundingClientRect(); const mouseX = e.originalEvent.clientX; // determine if dropping on left or right half of container - const onLeftSide = mouseX < (containerRect.left + containerRect.width / 2); + const onLeftSide = mouseX < containerRect.left + containerRect.width / 2; - const $tabs = $container.find('.tab'); + const $tabs = $container.find(".tab"); if ($tabs.length) { // choose the first tab for left drop, last tab for right drop const targetTab = onLeftSide ? $tabs.first()[0] : $tabs.last()[0]; updateDragIndicator(targetTab, onLeftSide); } } + + // Create the extended drop zone if we're actively dragging + if (draggedTab) { + createOuterDropZone(); + } }); // handle drop on the container (empty space) @@ -153,14 +204,15 @@ define(function (require, exports, module) { } // hide the drag indicator updateDragIndicator(null); + removeOuterDropZone(); // get container dimensions to determine drop position const containerRect = this.getBoundingClientRect(); const mouseX = e.originalEvent.clientX; // determine if dropping on left or right half of container - const onLeftSide = mouseX < (containerRect.left + containerRect.width / 2); + const onLeftSide = mouseX < containerRect.left + containerRect.width / 2; - const $tabs = $container.find('.tab'); + const $tabs = $container.find(".tab"); if ($tabs.length) { // If dropping on left half, target the first tab; otherwise, target the last tab const targetTab = onLeftSide ? $tabs.first()[0] : $tabs.last()[0]; @@ -184,8 +236,86 @@ define(function (require, exports, module) { } } }); - } + /** + * Updates the drag indicator when mouse is in the extended zone (outside actual tab bar) + * @param {jQuery} $container - The tab bar container + * @param {number} mouseX - Current mouse X position + */ + function updateDragIndicatorFromOuterZone($container, mouseX) { + const containerRect = $container[0].getBoundingClientRect(); + const $tabs = $container.find(".tab"); + + if ($tabs.length) { + // Determine if dropping on left half or right half + let onLeftSide = true; + let targetTab; + + // If beyond the right edge, use the last tab + if (mouseX > containerRect.right) { + targetTab = $tabs.last()[0]; + onLeftSide = false; + } + // If beyond the left edge, use the first tab + else if (mouseX < containerRect.left) { + targetTab = $tabs.first()[0]; + onLeftSide = true; + } + // If within bounds, find the closest tab + else { + onLeftSide = mouseX < containerRect.left + containerRect.width / 2; + targetTab = onLeftSide ? $tabs.first()[0] : $tabs.last()[0]; + } + + updateDragIndicator(targetTab, onLeftSide); + } + } + + /** + * Handles drops that occur in the extended drop zone + * @param {jQuery} $container - The tab bar container + * @param {number} mouseX - Current mouse X position + */ + function handleOuterZoneDrop($container, mouseX) { + const containerRect = $container[0].getBoundingClientRect(); + const $tabs = $container.find(".tab"); + + if ($tabs.length && draggedTab) { + // Determine drop position similar to updateDragIndicatorFromOuterZone + let onLeftSide = true; + let targetTab; + + if (mouseX > containerRect.right) { + targetTab = $tabs.last()[0]; + onLeftSide = false; + } else if (mouseX < containerRect.left) { + targetTab = $tabs.first()[0]; + onLeftSide = true; + } else { + onLeftSide = mouseX < containerRect.left + containerRect.width / 2; + targetTab = onLeftSide ? $tabs.first()[0] : $tabs.last()[0]; + } + + // Process the drop + const isSecondPane = $container.attr("id") === "phoenix-tab-bar-2"; + const targetPaneId = isSecondPane ? "second-pane" : "first-pane"; + const draggedPath = $(draggedTab).attr("data-path"); + const targetPath = $(targetTab).attr("data-path"); + + if (dragSourcePane !== targetPaneId) { + // cross-pane drop + moveTabBetweenPanes(dragSourcePane, targetPaneId, draggedPath, targetPath, onLeftSide); + } else { + // same pane drop + moveWorkingSetItem(targetPaneId, draggedPath, targetPath, onLeftSide); + } + } + + // Clean up + updateDragIndicator(null); + removeOuterDropZone(); + } + } /** * enhanced auto-scroll function for container when the mouse is near its left or right edge @@ -196,35 +326,38 @@ define(function (require, exports, module) { */ function autoScrollContainer(container, mouseX) { const rect = container.getBoundingClientRect(); - const edgeThreshold = 50; // teh threshold distance from the edge + const edgeThreshold = 100; // Increased threshold for edge detection (was 50) + const outerThreshold = 50; // Distance outside the container that still triggers scrolling - // Calculate distance from edges - const distanceFromLeft = mouseX - rect.left; - const distanceFromRight = rect.right - mouseX; + // Calculate distance from edges, allowing for mouse to be slightly outside bounds + const distanceFromLeft = mouseX - (rect.left - outerThreshold); + const distanceFromRight = rect.right + outerThreshold - mouseX; // Determine scroll speed based on distance from edge (closer = faster scroll) let scrollSpeed = 0; - if (distanceFromLeft < edgeThreshold) { - // exponential scroll speed: faster as you get closer to the edge - scrollSpeed = -Math.pow(1 - (distanceFromLeft / edgeThreshold), 2) * 15; - } else if (distanceFromRight < edgeThreshold) { - scrollSpeed = Math.pow(1 - (distanceFromRight / edgeThreshold), 2) * 15; + // Only activate scrolling when within the threshold (including the outer buffer) + if (distanceFromLeft < edgeThreshold + outerThreshold && mouseX < rect.right) { + // Non-linear scroll speed: faster as you get closer to the edge + scrollSpeed = -Math.pow(1 - distanceFromLeft / (edgeThreshold + outerThreshold), 2) * 25; + } else if (distanceFromRight < edgeThreshold + outerThreshold && mouseX > rect.left) { + scrollSpeed = Math.pow(1 - distanceFromRight / (edgeThreshold + outerThreshold), 2) * 25; } - // apply scrolling if needed + // Apply scrolling if needed if (scrollSpeed !== 0) { container.scrollLeft += scrollSpeed; // If we're already at the edge, don't keep trying to scroll - if ((scrollSpeed < 0 && container.scrollLeft <= 0) || - (scrollSpeed > 0 && container.scrollLeft >= container.scrollWidth - container.clientWidth)) { + if ( + (scrollSpeed < 0 && container.scrollLeft <= 0) || + (scrollSpeed > 0 && container.scrollLeft >= container.scrollWidth - container.clientWidth) + ) { return; } } } - /** * Handle the start of a drag operation * Stores the tab being dragged and adds visual styling @@ -237,14 +370,14 @@ define(function (require, exports, module) { // set data transfer (required for Firefox) // Firefox requires data to be set for the drag operation to work - e.originalEvent.dataTransfer.effectAllowed = 'move'; - e.originalEvent.dataTransfer.setData('text/html', this.innerHTML); + e.originalEvent.dataTransfer.effectAllowed = "move"; + e.originalEvent.dataTransfer.setData("text/html", this.innerHTML); // Store which pane this tab came from dragSourcePane = $(this).closest("#phoenix-tab-bar-2").length > 0 ? "second-pane" : "first-pane"; // Add dragging class for styling - $(this).addClass('dragging'); + $(this).addClass("dragging"); // Use a timeout to let the dragging class apply before taking measurements // This ensures visual updates are applied before we calculate positions @@ -253,7 +386,6 @@ define(function (require, exports, module) { }, 0); } - /** * Handle the dragover event to enable drop * Updates the visual indicator showing where the tab will be dropped @@ -264,13 +396,13 @@ define(function (require, exports, module) { if (e.preventDefault) { e.preventDefault(); // Allows us to drop } - e.originalEvent.dataTransfer.dropEffect = 'move'; + e.originalEvent.dataTransfer.dropEffect = "move"; // Update the drag indicator position // We need to determine if it should be on the left or right side of the target tab const targetRect = this.getBoundingClientRect(); const mouseX = e.originalEvent.clientX; - const midPoint = targetRect.left + (targetRect.width / 2); + const midPoint = targetRect.left + targetRect.width / 2; const onLeftSide = mouseX < midPoint; updateDragIndicator(this, onLeftSide); @@ -278,7 +410,6 @@ define(function (require, exports, module) { return false; } - /** * Handle entering a potential drop target * Applies styling to indicate the current drop target @@ -287,10 +418,9 @@ define(function (require, exports, module) { */ function handleDragEnter(e) { dragOverTab = this; - $(this).addClass('drag-target'); + $(this).addClass("drag-target"); } - /** * Handle leaving a potential drop target * Removes styling when no longer hovering over a drop target @@ -302,14 +432,13 @@ define(function (require, exports, module) { // Only remove the class if we're truly leaving this tab // This prevents flickering when moving over child elements if (!$(this).is(relatedTarget) && !$(this).has(relatedTarget).length) { - $(this).removeClass('drag-target'); + $(this).removeClass("drag-target"); if (dragOverTab === this) { dragOverTab = null; } } } - /** * Handle dropping a tab onto a target * Moves the file in the working set to the new position @@ -333,7 +462,7 @@ define(function (require, exports, module) { // Determine if we're dropping to the left or right of the target const targetRect = this.getBoundingClientRect(); const mouseX = e.originalEvent.clientX; - const midPoint = targetRect.left + (targetRect.width / 2); + const midPoint = targetRect.left + targetRect.width / 2; const onLeftSide = mouseX < midPoint; // Check if dragging between different panes @@ -348,7 +477,6 @@ define(function (require, exports, module) { return false; } - /** * Handle the end of a drag operation * Cleans up classes and resets state variables @@ -356,7 +484,7 @@ define(function (require, exports, module) { * @param {Event} e - The event object */ function handleDragEnd(e) { - $(".tab").removeClass('dragging drag-target'); + $(".tab").removeClass("dragging drag-target"); updateDragIndicator(null); draggedTab = null; dragOverTab = null; @@ -367,12 +495,15 @@ define(function (require, exports, module) { clearInterval(scrollInterval); scrollInterval = null; } - } + // Remove the extended drop zone if it exists + $("#tab-drag-extended-zone").remove(); + } /** * Update the drag indicator position and visibility * The indicator shows where the tab will be dropped + * Ensures the indicator stays within the bounds of the tab bar * * @param {HTMLElement} targetTab - The tab being dragged over, or null to hide * @param {Boolean} onLeftSide - Whether the indicator should be on the left or right side @@ -382,20 +513,30 @@ define(function (require, exports, module) { dragIndicator.hide(); return; } + // Get the target tab's position and size const targetRect = targetTab.getBoundingClientRect(); + + // Find the containing tab bar to ensure the indicator stays within bounds + const $tabBar = $(targetTab).closest("#phoenix-tab-bar, #phoenix-tab-bar-2"); + const tabBarRect = $tabBar[0] ? $tabBar[0].getBoundingClientRect() : null; + if (onLeftSide) { // Position indicator at the left edge of the target tab + // Ensure it doesn't go beyond the tab bar's left edge + const leftPos = tabBarRect ? Math.max(targetRect.left, tabBarRect.left) : targetRect.left; dragIndicator.css({ top: targetRect.top, - left: targetRect.left, + left: leftPos, height: targetRect.height }); } else { // Position indicator at the right edge of the target tab + // Ensure it doesn't go beyond the tab bar's right edge + const rightPos = tabBarRect ? Math.min(targetRect.right, tabBarRect.right) : targetRect.right; dragIndicator.css({ top: targetRect.top, - left: targetRect.right, + left: rightPos, height: targetRect.height }); } @@ -478,10 +619,7 @@ define(function (require, exports, module) { // Only continue if we found the dragged file if (draggedIndex !== -1 && draggedFile) { // Remove the file from source pane - CommandManager.execute( - Commands.FILE_CLOSE, - { file: draggedFile, paneId: sourcePaneId } - ); + CommandManager.execute(Commands.FILE_CLOSE, { file: draggedFile, paneId: sourcePaneId }); // Calculate where to add it in the target pane let targetInsertIndex; @@ -521,7 +659,6 @@ define(function (require, exports, module) { setupEmptyPaneDropTarget($secondPaneHolder, "second-pane"); } - /** * sets up the whole pane as a drop target when it has no tabs * @@ -546,7 +683,7 @@ define(function (require, exports, module) { $(this).addClass("empty-pane-drop-target"); // set the drop effect - e.originalEvent.dataTransfer.dropEffect = 'move'; + e.originalEvent.dataTransfer.dropEffect = "move"; } }); @@ -571,8 +708,8 @@ define(function (require, exports, module) { const draggedPath = $(draggedTab).attr("data-path"); // Determine source pane - const sourcePaneId = $(draggedTab) - .closest("#phoenix-tab-bar-2").length > 0 ? "second-pane" : "first-pane"; + const sourcePaneId = + $(draggedTab).closest("#phoenix-tab-bar-2").length > 0 ? "second-pane" : "first-pane"; // we don't want to do anything if dropping in the same pane if (sourcePaneId !== paneId) { @@ -589,10 +726,7 @@ define(function (require, exports, module) { if (draggedFile) { // close in the source pane - CommandManager.execute( - Commands.FILE_CLOSE, - { file: draggedFile, paneId: sourcePaneId } - ); + CommandManager.execute(Commands.FILE_CLOSE, { file: draggedFile, paneId: sourcePaneId }); // and open in the target pane MainViewManager.addToWorkingSet(paneId, draggedFile); @@ -609,7 +743,6 @@ define(function (require, exports, module) { }); } - module.exports = { init }; diff --git a/src/styles/Extn-TabBar.less b/src/styles/Extn-TabBar.less index 5400a58ef0..b458305207 100644 --- a/src/styles/Extn-TabBar.less +++ b/src/styles/Extn-TabBar.less @@ -307,30 +307,30 @@ .tab-drag-indicator { position: fixed; width: 2px; - background-color: #0078D7; + background-color: rgba(0, 120, 215, 0.7); pointer-events: none; z-index: 10001; - box-shadow: 0 0 3px rgba(0, 120, 215, 0.5); + box-shadow: 0 0 3px rgba(0, 120, 215, 0.4); display: none; - animation: pulse 1.5s infinite; + animation: pulse 2s infinite; } .dark .tab-drag-indicator { - background-color: #75BEFF; - box-shadow: 0 0 3px rgba(117, 190, 255, 0.5); + background-color: rgba(117, 190, 255, 0.7); + box-shadow: 0 0 3px rgba(117, 190, 255, 0.4); } @keyframes pulse { 0% { - opacity: 0.7; + opacity: 0.5; } 50% { - opacity: 1; + opacity: 0.7; } 100% { - opacity: 0.7; + opacity: 0.5; } } @@ -414,3 +414,9 @@ .tab-name-container.placeholder-name { font-style: italic; } + +/* this is for invisible extended drag zone for tabs */ +#tab-drag-extended-zone { + background-color: transparent; + pointer-events: all; +} From 4b403b3cb8b5602ad3c881f4eb7b9ce53b75289e Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 17 May 2025 19:03:55 +0530 Subject: [PATCH 08/13] fix: show active tab at all cases by replacing the last tab from the preference. numberOfTabs --- src/extensionsIntegrated/TabBar/main.js | 173 +++++++++++++----------- 1 file changed, 95 insertions(+), 78 deletions(-) diff --git a/src/extensionsIntegrated/TabBar/main.js b/src/extensionsIntegrated/TabBar/main.js index eaec425696..a59c4f7733 100644 --- a/src/extensionsIntegrated/TabBar/main.js +++ b/src/extensionsIntegrated/TabBar/main.js @@ -42,8 +42,6 @@ define(function (require, exports, module) { const TabBarHTML = require("text!./html/tabbar-pane.html"); const TabBarHTML2 = require("text!./html/tabbar-second-pane.html"); - - /** * This holds the tab bar element * For tab bar structure, refer to `./html/tabbar-pane.html` and `./html/tabbar-second-pane.html` @@ -54,7 +52,6 @@ define(function (require, exports, module) { let $tabBar = null; let $tabBar2 = null; - /** * This function is responsible to take all the files from the working set and gets the working sets ready * This is placed here instead of helper.js because it modifies the working sets @@ -68,7 +65,6 @@ define(function (require, exports, module) { // to make sure atleast one pane is open if (paneList && paneList.length > 0) { - // this gives the working set of the first pane const currFirstPaneWorkingSet = MainViewManager.getWorkingSet(paneList[0]); @@ -108,8 +104,6 @@ define(function (require, exports, module) { } } - - /** * Responsible for creating the tab element * Note: this creates a tab (for a single file) not the tab bar @@ -128,11 +122,11 @@ define(function (require, exports, module) { const activePathInPane = activeFileInPane ? activeFileInPane.fullPath : null; // Check if this file is active in its pane - const isActive = (entry.path === activePathInPane); + const isActive = entry.path === activePathInPane; // Current active pane (used to determine whether to add the blue underline) const currentActivePane = MainViewManager.getActivePaneId(); - const isPaneActive = (paneId === currentActivePane); + const isPaneActive = paneId === currentActivePane; const isDirty = Helper._isFileModified(FileSystem.getFileForPath(entry.path)); const isPlaceholder = entry.isPlaceholder === true; @@ -140,9 +134,9 @@ define(function (require, exports, module) { // create tab with all the appropriate classes const $tab = $( `
@@ -153,13 +147,13 @@ define(function (require, exports, module) { // Add the file icon const $icon = Helper._getFileIcon(entry); - $tab.find('.tab-icon').append($icon); + $tab.find(".tab-icon").append($icon); // Check if we have a directory part in the displayName - const $tabName = $tab.find('.tab-name'); + const $tabName = $tab.find(".tab-name"); if (entry.displayName && entry.displayName !== entry.name) { // Split the displayName into directory and filename parts - const parts = entry.displayName.split('/'); + const parts = entry.displayName.split("/"); const dirName = parts[0]; const fileName = parts[1]; @@ -174,39 +168,38 @@ define(function (require, exports, module) { if (isActive && !isPaneActive) { // if it's active but in a non-active pane, we add a special class // to style differently in CSS to indicate that it's active but not in the active pane - $tab.addClass('active-in-inactive-pane'); + $tab.addClass("active-in-inactive-pane"); } // if this is a placeholder tab in inactive pane, we need to use the brown styling // instead of the blue one for active tabs if (isPlaceholder && isActive && !isPaneActive) { - $tab.removeClass('active'); - $tab.addClass('active-in-inactive-pane'); + $tab.removeClass("active"); + $tab.addClass("active-in-inactive-pane"); } return $tab; } - /** * Creates the tab bar and adds it to the DOM */ function createTabBar() { - if (!Preference.tabBarEnabled || Preference.numberOfTabs === 0) { + if (!Preference.tabBarEnabled || Preference.tabBarNumberOfTabs === 0) { + cleanupTabBar(); return; } // clean up any existing tab bars first and start fresh cleanupTabBar(); - const $paneHeader = $('.pane-header'); + const $paneHeader = $(".pane-header"); if ($paneHeader.length === 1) { $tabBar = $(TabBarHTML); // since we need to add the tab bar before the editor which has .not-editor class $(".pane-header").after($tabBar); WorkspaceManager.recomputeLayout(true); updateTabs(); - } else if ($paneHeader.length === 2) { $tabBar = $(TabBarHTML); $tabBar2 = $(TabBarHTML2); @@ -220,7 +213,6 @@ define(function (require, exports, module) { } } - /** * This function updates the tabs in the tab bar * It is called when the working set changes. So instead of creating a new tab bar, we just update the existing one @@ -229,6 +221,12 @@ define(function (require, exports, module) { // Get all files from the working set. refer to `global.js` getAllFilesFromWorkingSet(); + // just to make sure that the number of tabs is not set to 0 + if (Preference.tabBarNumberOfTabs === 0) { + cleanupTabBar(); + return; + } + // Check for active files not in working set in any pane const activePane = MainViewManager.getActivePaneId(); const firstPaneFile = MainViewManager.getCurrentlyViewedFile("first-pane"); @@ -239,8 +237,7 @@ define(function (require, exports, module) { let secondPanePlaceholder = null; // Check if active file in first pane is not in the working set - if (firstPaneFile && - !Global.firstPaneWorkingSet.some(entry => entry.path === firstPaneFile.fullPath)) { + if (firstPaneFile && !Global.firstPaneWorkingSet.some((entry) => entry.path === firstPaneFile.fullPath)) { firstPanePlaceholder = { path: firstPaneFile.fullPath, name: firstPaneFile.name, @@ -250,8 +247,7 @@ define(function (require, exports, module) { } // Check if active file in second pane is not in the working set - if (secondPaneFile && - !Global.secondPaneWorkingSet.some(entry => entry.path === secondPaneFile.fullPath)) { + if (secondPaneFile && !Global.secondPaneWorkingSet.some((entry) => entry.path === secondPaneFile.fullPath)) { secondPanePlaceholder = { path: secondPaneFile.fullPath, name: secondPaneFile.name, @@ -263,9 +259,7 @@ define(function (require, exports, module) { // check for duplicate file names between placeholder tabs and working set entries if (firstPanePlaceholder) { // if any working set file has the same name as the placeholder - const hasDuplicate = Global.firstPaneWorkingSet.some(entry => - entry.name === firstPanePlaceholder.name - ); + const hasDuplicate = Global.firstPaneWorkingSet.some((entry) => entry.name === firstPanePlaceholder.name); if (hasDuplicate) { // extract directory name from path @@ -280,9 +274,7 @@ define(function (require, exports, module) { } if (secondPanePlaceholder) { - const hasDuplicate = Global.secondPaneWorkingSet.some(entry => - entry.name === secondPanePlaceholder.name - ); + const hasDuplicate = Global.secondPaneWorkingSet.some((entry) => entry.name === secondPanePlaceholder.name); if (hasDuplicate) { const path = secondPanePlaceholder.path; @@ -295,22 +287,25 @@ define(function (require, exports, module) { } // Create tab bar if there's a placeholder or a file in the working set - if ((Global.firstPaneWorkingSet.length > 0 || firstPanePlaceholder) && - (!$('#phoenix-tab-bar').length || $('#phoenix-tab-bar').is(':hidden'))) { + if ( + (Global.firstPaneWorkingSet.length > 0 || firstPanePlaceholder) && + (!$("#phoenix-tab-bar").length || $("#phoenix-tab-bar").is(":hidden")) + ) { createTabBar(); } - if ((Global.secondPaneWorkingSet.length > 0 || secondPanePlaceholder) && - (!$('#phoenix-tab-bar-2').length || $('#phoenix-tab-bar-2').is(':hidden'))) { + if ( + (Global.secondPaneWorkingSet.length > 0 || secondPanePlaceholder) && + (!$("#phoenix-tab-bar-2").length || $("#phoenix-tab-bar-2").is(":hidden")) + ) { createTabBar(); } - const $firstTabBar = $('#phoenix-tab-bar'); + const $firstTabBar = $("#phoenix-tab-bar"); // Update first pane's tabs if ($firstTabBar.length) { $firstTabBar.empty(); if (Global.firstPaneWorkingSet.length > 0 || firstPanePlaceholder) { - // get the count of tabs that we want to display in the tab bar (from preference settings) // from preference settings or working set whichever smaller let tabsCountP1 = Math.min(Global.firstPaneWorkingSet.length, Preference.tabBarNumberOfTabs); @@ -321,7 +316,28 @@ define(function (require, exports, module) { tabsCountP1 = Global.firstPaneWorkingSet.length; } - let displayedEntries = Global.firstPaneWorkingSet.slice(0, tabsCountP1); + let displayedEntries = []; + + // check if active file is in the working set but would be excluded by tab count + if (firstPaneFile && Preference.tabBarNumberOfTabs > 0) { + const activeFileIndex = Global.firstPaneWorkingSet.findIndex( + (entry) => entry.path === firstPaneFile.fullPath + ); + + if (activeFileIndex >= 0 && activeFileIndex >= Preference.tabBarNumberOfTabs) { + // active file is in working set but would be excluded by tab count + // Show active file and one less from the top N files + displayedEntries = [ + ...Global.firstPaneWorkingSet.slice(0, Preference.tabBarNumberOfTabs - 1), + Global.firstPaneWorkingSet[activeFileIndex] + ]; + } else { + // Active file is either not in working set or already included in top N files + displayedEntries = Global.firstPaneWorkingSet.slice(0, tabsCountP1); + } + } else { + displayedEntries = Global.firstPaneWorkingSet.slice(0, tabsCountP1); + } // Add working set tabs displayedEntries.forEach(function (entry) { @@ -335,18 +351,34 @@ define(function (require, exports, module) { } } - const $secondTabBar = $('#phoenix-tab-bar-2'); + const $secondTabBar = $("#phoenix-tab-bar-2"); // Update second pane's tabs if ($secondTabBar.length) { $secondTabBar.empty(); if (Global.secondPaneWorkingSet.length > 0 || secondPanePlaceholder) { - let tabsCountP2 = Math.min(Global.secondPaneWorkingSet.length, Preference.tabBarNumberOfTabs); if (Preference.tabBarNumberOfTabs < 0) { tabsCountP2 = Global.secondPaneWorkingSet.length; } - let displayedEntries2 = Global.secondPaneWorkingSet.slice(0, tabsCountP2); + let displayedEntries2 = []; + + if (secondPaneFile && Preference.tabBarNumberOfTabs > 0) { + const activeFileIndex = Global.secondPaneWorkingSet.findIndex( + (entry) => entry.path === secondPaneFile.fullPath + ); + + if (activeFileIndex >= 0 && activeFileIndex >= Preference.tabBarNumberOfTabs) { + displayedEntries2 = [ + ...Global.secondPaneWorkingSet.slice(0, Preference.tabBarNumberOfTabs - 1), + Global.secondPaneWorkingSet[activeFileIndex] + ]; + } else { + displayedEntries2 = Global.secondPaneWorkingSet.slice(0, tabsCountP2); + } + } else { + displayedEntries2 = Global.secondPaneWorkingSet.slice(0, tabsCountP2); + } // Add working set tabs displayedEntries2.forEach(function (entry) { @@ -361,12 +393,12 @@ define(function (require, exports, module) { } // if no files are present in a pane and no placeholder, we want to hide the tab bar for that pane - if (Global.firstPaneWorkingSet.length === 0 && !firstPanePlaceholder && ($('#phoenix-tab-bar'))) { - Helper._hideTabBar($('#phoenix-tab-bar'), $('#overflow-button')); + if (Global.firstPaneWorkingSet.length === 0 && !firstPanePlaceholder && $("#phoenix-tab-bar")) { + Helper._hideTabBar($("#phoenix-tab-bar"), $("#overflow-button")); } - if (Global.secondPaneWorkingSet.length === 0 && !secondPanePlaceholder && ($('#phoenix-tab-bar-2'))) { - Helper._hideTabBar($('#phoenix-tab-bar-2'), $('#overflow-button-2')); + if (Global.secondPaneWorkingSet.length === 0 && !secondPanePlaceholder && $("#phoenix-tab-bar-2")) { + Helper._hideTabBar($("#phoenix-tab-bar-2"), $("#overflow-button-2")); } // Now that tabs are updated, scroll to the active tab if necessary. @@ -394,10 +426,9 @@ define(function (require, exports, module) { } // handle drag and drop - DragDrop.init($('#phoenix-tab-bar'), $('#phoenix-tab-bar-2')); + DragDrop.init($("#phoenix-tab-bar"), $("#phoenix-tab-bar-2")); } - /** * Removes existing tab bar and cleans up */ @@ -415,16 +446,14 @@ define(function (require, exports, module) { WorkspaceManager.recomputeLayout(true); } - /** * handle click events on the tabs to open the file */ function handleTabClick() { - // delegate event handling for both tab bars $(document).on("click", ".phoenix-tab-bar .tab", function (event) { // check if the clicked element is the close button - if ($(event.target).hasClass('fa-times') || $(event.target).closest('.tab-close').length) { + if ($(event.target).hasClass("fa-times") || $(event.target).closest(".tab-close").length) { // Get the file path from the data-path attribute of the parent tab const filePath = $(this).attr("data-path"); @@ -436,10 +465,7 @@ define(function (require, exports, module) { // get the file object const fileObj = FileSystem.getFileForPath(filePath); // close the file - CommandManager.execute( - Commands.FILE_CLOSE, - { file: fileObj, paneId: paneId } - ); + CommandManager.execute(Commands.FILE_CLOSE, { file: fileObj, paneId: paneId }); // Prevent default behavior event.preventDefault(); @@ -450,7 +476,7 @@ define(function (require, exports, module) { // delegate event handling for both tab bars $(document).on("mousedown", ".phoenix-tab-bar .tab", function (event) { - if ($(event.target).hasClass('fa-times') || $(event.target).closest('.tab-close').length) { + if ($(event.target).hasClass("fa-times") || $(event.target).closest(".tab-close").length) { return; } // Get the file path from the data-path attribute @@ -461,11 +487,11 @@ define(function (require, exports, module) { const isSecondPane = $(this).closest("#phoenix-tab-bar-2").length > 0; const paneId = isSecondPane ? "second-pane" : "first-pane"; const currentActivePane = MainViewManager.getActivePaneId(); - const isPaneActive = (paneId === currentActivePane); + const isPaneActive = paneId === currentActivePane; const currentFile = MainViewManager.getCurrentlyViewedFile(currentActivePane); // Check if this is a placeholder tab - if ($(this).hasClass('placeholder')) { + if ($(this).hasClass("placeholder")) { // Add the file to the working set when placeholder tab is clicked const fileObj = FileSystem.getFileForPath(filePath); MainViewManager.addToWorkingSet(paneId, fileObj); @@ -497,7 +523,6 @@ define(function (require, exports, module) { }); } - // debounce is used to prevent rapid consecutive calls to updateTabs, // which was causing integration tests to fail in Firefox. Without it, // the event fires too frequently when switching editors, leading to unexpected behavior @@ -545,8 +570,7 @@ define(function (require, exports, module) { // Update UI if ($tabBar) { const $tab = $tabBar.find(`.tab[data-path="${filePath}"]`); - $tab.toggleClass('dirty', doc.isDirty); - + $tab.toggleClass("dirty", doc.isDirty); // Update the working set data // First pane @@ -558,11 +582,10 @@ define(function (require, exports, module) { } } - // Also update the $tab2 if it exists if ($tabBar2) { const $tab2 = $tabBar2.find(`.tab[data-path="${filePath}"]`); - $tab2.toggleClass('dirty', doc.isDirty); + $tab2.toggleClass("dirty", doc.isDirty); // Second pane for (let i = 0; i < Global.secondPaneWorkingSet.length; i++) { @@ -575,7 +598,6 @@ define(function (require, exports, module) { }); } - /** * This is called when the tab bar preference is changed either, * from the preferences file or the menu bar @@ -601,17 +623,13 @@ define(function (require, exports, module) { * for toggling the tab bar from the menu bar */ function _registerCommands() { - CommandManager.register( - Strings.CMD_TOGGLE_TABBAR, - Commands.TOGGLE_TABBAR, - () => { - const currentPref = PreferencesManager.get(Preference.PREFERENCES_TAB_BAR); - PreferencesManager.set(Preference.PREFERENCES_TAB_BAR, { - ...currentPref, - showTabBar: !currentPref.showTabBar - }); - } - ); + CommandManager.register(Strings.CMD_TOGGLE_TABBAR, Commands.TOGGLE_TABBAR, () => { + const currentPref = PreferencesManager.get(Preference.PREFERENCES_TAB_BAR); + PreferencesManager.set(Preference.PREFERENCES_TAB_BAR, { + ...currentPref, + showTabBar: !currentPref.showTabBar + }); + }); } /** @@ -620,7 +638,6 @@ define(function (require, exports, module) { * when its scrolled down, the tab bar will scroll to the right */ function setupTabBarScrolling() { - // common handler for both the tab bars function handleMouseWheel(e) { // get the tab bar element that is being scrolled @@ -640,7 +657,7 @@ define(function (require, exports, module) { } // attach the wheel event handler to both tab bars - $(document).on('wheel', '#phoenix-tab-bar, #phoenix-tab-bar-2', handleMouseWheel); + $(document).on("wheel", "#phoenix-tab-bar, #phoenix-tab-bar-2", handleMouseWheel); } AppInit.appReady(function () { @@ -662,7 +679,7 @@ define(function (require, exports, module) { handleTabClick(); Overflow.init(); - DragDrop.init($('#phoenix-tab-bar'), $('#phoenix-tab-bar-2')); + DragDrop.init($("#phoenix-tab-bar"), $("#phoenix-tab-bar-2")); // setup the mouse wheel scrolling setupTabBarScrolling(); From 35cc17b4d460863a55aabf6849031233d9a824eb Mon Sep 17 00:00:00 2001 From: Pluto Date: Sun, 18 May 2025 00:04:12 +0530 Subject: [PATCH 09/13] fix: show display path in tabs instead of tauri path --- src/extensionsIntegrated/TabBar/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extensionsIntegrated/TabBar/main.js b/src/extensionsIntegrated/TabBar/main.js index a59c4f7733..20bd8a4149 100644 --- a/src/extensionsIntegrated/TabBar/main.js +++ b/src/extensionsIntegrated/TabBar/main.js @@ -138,7 +138,7 @@ define(function (require, exports, module) { ${isDirty ? "dirty" : ""} ${isPlaceholder ? "placeholder" : ""}" data-path="${entry.path}" - title="${entry.path}"> + title="${Phoenix.app.getDisplayPath(entry.path)}">
From 4f49f8e512fa81debd9f776ce811d33a76332937 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sun, 18 May 2025 15:19:29 +0530 Subject: [PATCH 10/13] feat: show git change markers on tabs --- src/extensions/default/Git/main.js | 7 +- .../default/Git/src/TabBarIntegration.js | 88 +++++++++++++++++++ src/extensionsIntegrated/TabBar/main.js | 33 ++++++- src/styles/Extn-TabBar.less | 34 ++++++- 4 files changed, 155 insertions(+), 7 deletions(-) create mode 100644 src/extensions/default/Git/src/TabBarIntegration.js diff --git a/src/extensions/default/Git/main.js b/src/extensions/default/Git/main.js index e1921ca047..aaf38e8458 100644 --- a/src/extensions/default/Git/main.js +++ b/src/extensions/default/Git/main.js @@ -27,7 +27,8 @@ define(function (require, exports, module) { "src/History", "src/NoRepo", "src/ProjectTreeMarks", - "src/Remotes" + "src/Remotes", + "src/TabBarIntegration" ]; require(modules); @@ -48,10 +49,12 @@ define(function (require, exports, module) { // export API's for other extensions if (typeof window === "object") { + const TabBarIntegration = require("src/TabBarIntegration"); window.phoenixGitEvents = { EventEmitter: EventEmitter, Events: Events, - Git + Git, + TabBarIntegration }; } }); diff --git a/src/extensions/default/Git/src/TabBarIntegration.js b/src/extensions/default/Git/src/TabBarIntegration.js new file mode 100644 index 0000000000..e904922542 --- /dev/null +++ b/src/extensions/default/Git/src/TabBarIntegration.js @@ -0,0 +1,88 @@ +define(function (require) { + const EventEmitter = require("src/EventEmitter"); + const Events = require("src/Events"); + const Git = require("src/git/Git"); + const Preferences = require("src/Preferences"); + + // the cache of file statuses by path + let fileStatusCache = {}; + + /** + * this function is responsible to get the Git status for a file path + * + * @param {string} fullPath - the file path + * @returns {Array|null} - Array of status strings or null if no status + */ + function getFileStatus(fullPath) { + return fileStatusCache[fullPath] || null; + } + + /** + * whether the file is modified or not + * + * @param {string} fullPath - the file path + * @returns {boolean} - True if the file is modified otherwise false + */ + function isModified(fullPath) { + const status = getFileStatus(fullPath); + if (!status) { + return false; + } + return ( + status.some( + (statusType) => + statusType === Git.FILE_STATUS.MODIFIED || + statusType === Git.FILE_STATUS.RENAMED || + statusType === Git.FILE_STATUS.COPIED + ) && !status.includes(Git.FILE_STATUS.STAGED) + ); + } + + /** + * whether the file is untracked or not + * + * @param {string} fullPath - the file path + * @returns {boolean} - True if the file is untracked otherwise false + */ + function isUntracked(fullPath) { + const status = getFileStatus(fullPath); + if (!status) { + return false; + } + + return status.includes(Git.FILE_STATUS.UNTRACKED); + } + + + // Update file status cache when Git status results are received + EventEmitter.on(Events.GIT_STATUS_RESULTS, function (files) { + // reset the cache + fileStatusCache = {}; + + const gitRoot = Preferences.get("currentGitRoot"); + if (!gitRoot) { + return; + } + + // we need to update cache with new status results + files.forEach(function (entry) { + const fullPath = gitRoot + entry.file; + fileStatusCache[fullPath] = entry.status; + }); + + // notify that file statuses have been updated + EventEmitter.emit("GIT_FILE_STATUS_CHANGED", fileStatusCache); + }); + + // clear cache when Git is disabled + EventEmitter.on(Events.GIT_DISABLED, function () { + fileStatusCache = {}; + EventEmitter.emit("GIT_FILE_STATUS_CHANGED", fileStatusCache); + }); + + return { + getFileStatus: getFileStatus, + isModified: isModified, + isUntracked: isUntracked + }; +}); diff --git a/src/extensionsIntegrated/TabBar/main.js b/src/extensionsIntegrated/TabBar/main.js index 20bd8a4149..1eac0140fb 100644 --- a/src/extensionsIntegrated/TabBar/main.js +++ b/src/extensionsIntegrated/TabBar/main.js @@ -131,16 +131,39 @@ define(function (require, exports, module) { const isDirty = Helper._isFileModified(FileSystem.getFileForPath(entry.path)); const isPlaceholder = entry.isPlaceholder === true; + let gitStatus = ""; // this will be shown in the tooltip when a tab is hovered + let gitStatusClass = ""; // for styling + let gitStatusLetter = ""; // shown in the tab, either U or M + + if (window.phoenixGitEvents && window.phoenixGitEvents.TabBarIntegration) { + const TabBarIntegration = window.phoenixGitEvents.TabBarIntegration; + + // find the Git status + // if untracked we add the git-new class and U char + // if modified we add the git-modified class and M char + if (TabBarIntegration.isUntracked(entry.path)) { + gitStatus = "Untracked"; + gitStatusClass = "git-new"; + gitStatusLetter = "U"; + } else if (TabBarIntegration.isModified(entry.path)) { + gitStatus = "Modified"; + gitStatusClass = "git-modified"; + gitStatusLetter = "M"; + } + } + // create tab with all the appropriate classes const $tab = $( `
+ title="${Phoenix.app.getDisplayPath(entry.path)}${gitStatus ? " (" + gitStatus + ")" : ""}">
+ ${gitStatusLetter ? `
${gitStatusLetter}
` : ""}
` ); @@ -541,6 +564,12 @@ define(function (require, exports, module) { // For editor changes, update only the tabs. MainViewManager.on(MainViewManager.EVENT_CURRENT_FILE_CHANGE, debounceUpdateTabs); + // to listen for the Git status changes + // make sure that the git extension is available + if (window.phoenixGitEvents && window.phoenixGitEvents.EventEmitter) { + window.phoenixGitEvents.EventEmitter.on("GIT_FILE_STATUS_CHANGED", debounceUpdateTabs); + } + // For working set changes, update only the tabs. const events = [ "workingSetAdd", diff --git a/src/styles/Extn-TabBar.less b/src/styles/Extn-TabBar.less index b458305207..1d0f850cfc 100644 --- a/src/styles/Extn-TabBar.less +++ b/src/styles/Extn-TabBar.less @@ -137,6 +137,8 @@ font-size: 0.7rem; opacity: 0.7; font-weight: normal; + position: relative; + top: 0.1rem; } .tab.active { @@ -195,10 +197,10 @@ .tab::before { content: "•"; color: transparent; - font-size: 1.6rem; + font-size: 1.5rem; position: absolute; left: 0.4rem; - top: 0.25rem; + top: 0.33rem; } .tab.dirty::before { @@ -215,7 +217,7 @@ .tab-close { font-size: 0.75rem; - padding: 0.08rem 0.4rem; + padding: 0.1rem 0.4rem; margin-left: 0.25rem; color: #666; transition: all 0.2s ease; @@ -420,3 +422,29 @@ background-color: transparent; pointer-events: all; } + +/* ------ for the git status markers -------------- */ +.tab-git-status { + font-size: 0.9rem; + font-weight: bold; + display: flex; + align-items: center; + justify-content: center; + margin-left: 0.33rem; +} + +.git-modified .tab-git-status { + color: #E3B551; +} + +.dark .git-modified .tab-git-status { + color: #E3B551; +} + +.git-new .tab-git-status { + color: #91CC41; +} + +.dark .git-new .tab-git-status { + color: #91CC41; +} From 9c39cc3dd5ef1b2311a5f23069d729a9cf456970 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sun, 18 May 2025 20:50:06 +0530 Subject: [PATCH 11/13] refactor: git markers styles in tabs --- src/extensionsIntegrated/TabBar/main.js | 1 - src/styles/Extn-TabBar.less | 25 ++++++++++++------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/extensionsIntegrated/TabBar/main.js b/src/extensionsIntegrated/TabBar/main.js index 1eac0140fb..228c42ddc1 100644 --- a/src/extensionsIntegrated/TabBar/main.js +++ b/src/extensionsIntegrated/TabBar/main.js @@ -163,7 +163,6 @@ define(function (require, exports, module) { title="${Phoenix.app.getDisplayPath(entry.path)}${gitStatus ? " (" + gitStatus + ")" : ""}">
- ${gitStatusLetter ? `
${gitStatusLetter}
` : ""}
` ); diff --git a/src/styles/Extn-TabBar.less b/src/styles/Extn-TabBar.less index 1d0f850cfc..e75cfc61ab 100644 --- a/src/styles/Extn-TabBar.less +++ b/src/styles/Extn-TabBar.less @@ -424,27 +424,26 @@ } /* ------ for the git status markers -------------- */ -.tab-git-status { - font-size: 0.9rem; - font-weight: bold; - display: flex; - align-items: center; - justify-content: center; - margin-left: 0.33rem; -} - -.git-modified .tab-git-status { +.tab.git-modified > .tab-icon:before { + content: "|"; color: #E3B551; + position: absolute; + margin-left: -4px; + margin-bottom: 5px; } -.dark .git-modified .tab-git-status { +.dark .tab.git-modified > .tab-icon:before { color: #E3B551; } -.git-new .tab-git-status { +.tab.git-new > .tab-icon:before { + content: "|"; color: #91CC41; + position: absolute; + margin-left: -4px; + margin-bottom: 5px; } -.dark .git-new .tab-git-status { +.dark .tab.git-new > .tab-icon:before { color: #91CC41; } From 5f4840f6537b62fa3e7674842d1487eba0c8457b Mon Sep 17 00:00:00 2001 From: Pluto Date: Sun, 18 May 2025 21:13:36 +0530 Subject: [PATCH 12/13] fix: git markers disappearing from tabs when files are staged --- .../default/Git/src/TabBarIntegration.js | 18 ++++++++++-------- src/extensionsIntegrated/TabBar/main.js | 3 --- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/extensions/default/Git/src/TabBarIntegration.js b/src/extensions/default/Git/src/TabBarIntegration.js index e904922542..24453f3166 100644 --- a/src/extensions/default/Git/src/TabBarIntegration.js +++ b/src/extensions/default/Git/src/TabBarIntegration.js @@ -28,13 +28,11 @@ define(function (require) { if (!status) { return false; } - return ( - status.some( - (statusType) => - statusType === Git.FILE_STATUS.MODIFIED || - statusType === Git.FILE_STATUS.RENAMED || - statusType === Git.FILE_STATUS.COPIED - ) && !status.includes(Git.FILE_STATUS.STAGED) + return status.some( + (statusType) => + statusType === Git.FILE_STATUS.MODIFIED || + statusType === Git.FILE_STATUS.RENAMED || + statusType === Git.FILE_STATUS.COPIED ); } @@ -50,7 +48,11 @@ define(function (require) { return false; } - return status.includes(Git.FILE_STATUS.UNTRACKED); + // return true if it's untracked or if it's newly added (which means it was untracked before staging) + return ( + status.includes(Git.FILE_STATUS.UNTRACKED) || + (status.includes(Git.FILE_STATUS.ADDED) && status.includes(Git.FILE_STATUS.STAGED)) + ); } diff --git a/src/extensionsIntegrated/TabBar/main.js b/src/extensionsIntegrated/TabBar/main.js index 228c42ddc1..9c43888f3f 100644 --- a/src/extensionsIntegrated/TabBar/main.js +++ b/src/extensionsIntegrated/TabBar/main.js @@ -133,7 +133,6 @@ define(function (require, exports, module) { let gitStatus = ""; // this will be shown in the tooltip when a tab is hovered let gitStatusClass = ""; // for styling - let gitStatusLetter = ""; // shown in the tab, either U or M if (window.phoenixGitEvents && window.phoenixGitEvents.TabBarIntegration) { const TabBarIntegration = window.phoenixGitEvents.TabBarIntegration; @@ -144,11 +143,9 @@ define(function (require, exports, module) { if (TabBarIntegration.isUntracked(entry.path)) { gitStatus = "Untracked"; gitStatusClass = "git-new"; - gitStatusLetter = "U"; } else if (TabBarIntegration.isModified(entry.path)) { gitStatus = "Modified"; gitStatusClass = "git-modified"; - gitStatusLetter = "M"; } } From 0c056c7b8aea03a2858345ecbf76b30786065c73 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sun, 18 May 2025 21:37:15 +0530 Subject: [PATCH 13/13] fix: git change markers color not properly visible in light theme --- src/styles/Extn-TabBar.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/Extn-TabBar.less b/src/styles/Extn-TabBar.less index e75cfc61ab..6f17f686d4 100644 --- a/src/styles/Extn-TabBar.less +++ b/src/styles/Extn-TabBar.less @@ -426,7 +426,7 @@ /* ------ for the git status markers -------------- */ .tab.git-modified > .tab-icon:before { content: "|"; - color: #E3B551; + color: #f1ae1c; position: absolute; margin-left: -4px; margin-bottom: 5px; @@ -438,7 +438,7 @@ .tab.git-new > .tab-icon:before { content: "|"; - color: #91CC41; + color: #69a01f; position: absolute; margin-left: -4px; margin-bottom: 5px;