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/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..24453f3166 --- /dev/null +++ b/src/extensions/default/Git/src/TabBarIntegration.js @@ -0,0 +1,90 @@ +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 + ); + } + + /** + * 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 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)) + ); + } + + + // 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/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/extensionsIntegrated/TabBar/main.js b/src/extensionsIntegrated/TabBar/main.js index eaec425696..9c43888f3f 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,23 +122,42 @@ 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; + let gitStatus = ""; // this will be shown in the tooltip when a tab is hovered + let gitStatusClass = ""; // for styling + + 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"; + } else if (TabBarIntegration.isModified(entry.path)) { + gitStatus = "Modified"; + gitStatusClass = "git-modified"; + } + } + // create tab with all the appropriate classes const $tab = $( `
+ title="${Phoenix.app.getDisplayPath(entry.path)}${gitStatus ? " (" + gitStatus + ")" : ""}">
@@ -153,13 +166,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 +187,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 +232,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 +240,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 +256,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 +266,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 +278,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 +293,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 +306,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 +335,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 +370,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 +412,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 +445,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 +465,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 +484,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 +495,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 +506,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 +542,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 @@ -516,6 +560,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", @@ -545,8 +595,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 +607,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 +623,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 +648,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 +663,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 +682,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 +704,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(); diff --git a/src/extensionsIntegrated/TabBar/more-options.js b/src/extensionsIntegrated/TabBar/more-options.js index f734741f44..8523b30b28 100644 --- a/src/extensionsIntegrated/TabBar/more-options.js +++ b/src/extensionsIntegrated/TabBar/more-options.js @@ -40,10 +40,13 @@ define(function (require, exports, module) { Strings.CLOSE_ALL_TABS, Strings.CLOSE_UNMODIFIED_TABS, "---", + 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 @@ -57,26 +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 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 @@ -97,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 @@ -123,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 @@ -155,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 @@ -192,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 @@ -200,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 @@ -218,6 +190,59 @@ 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 + * + * @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 }); + } + } + + /** + * "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 @@ -242,8 +267,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", "300px"); + // handle the option selection dropdown.on("select", function (e, item) { _handleSelection(item, filePath, paneId); @@ -279,6 +309,15 @@ 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.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 7b996f7da4..7228147d6e 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -433,6 +433,9 @@ 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", + "SHOW_IN_FILE_TREE": "Show in File Tree", // CodeInspection: errors/warnings "ERRORS_NO_FILE": "No File Open", diff --git a/src/styles/Extn-TabBar.less b/src/styles/Extn-TabBar.less index fb4288f541..6f17f686d4 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 { @@ -136,6 +137,8 @@ font-size: 0.7rem; opacity: 0.7; font-weight: normal; + position: relative; + top: 0.1rem; } .tab.active { @@ -191,13 +194,17 @@ background-color: #666; } -.tab.dirty::before { +.tab::before { content: "•"; - color: #888; - font-size: 1.6rem; + color: transparent; + font-size: 1.5rem; position: absolute; - left: 0.75rem; - top: 0.25rem; + left: 0.4rem; + top: 0.33rem; +} + +.tab.dirty::before { + color: #888; } .dark .tab.dirty::before { @@ -205,12 +212,12 @@ } .tab.dirty .tab-icon { - margin-left: 0.8rem; + margin-left: 0.7rem; } .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; @@ -302,30 +309,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; } } @@ -408,4 +415,35 @@ .dropdown-tab-item.placeholder-item .tab-name-container, .tab-name-container.placeholder-name { font-style: italic; -} \ No newline at end of file +} + +/* this is for invisible extended drag zone for tabs */ +#tab-drag-extended-zone { + background-color: transparent; + pointer-events: all; +} + +/* ------ for the git status markers -------------- */ +.tab.git-modified > .tab-icon:before { + content: "|"; + color: #f1ae1c; + position: absolute; + margin-left: -4px; + margin-bottom: 5px; +} + +.dark .tab.git-modified > .tab-icon:before { + color: #E3B551; +} + +.tab.git-new > .tab-icon:before { + content: "|"; + color: #69a01f; + position: absolute; + margin-left: -4px; + margin-bottom: 5px; +} + +.dark .tab.git-new > .tab-icon:before { + color: #91CC41; +}