diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json index 53454a8ed40..d74d3d57ed5 100644 --- a/extensions/theme-abyss/themes/abyss-color-theme.json +++ b/extensions/theme-abyss/themes/abyss-color-theme.json @@ -372,6 +372,7 @@ "tab.border": "#2b2b4a", // "tab.activeBackground": "", "tab.inactiveBackground": "#10192c", + "tab.modifiedBorder": "#0072bf", // "tab.activeForeground": "", // "tab.inactiveForeground": "", diff --git a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json index 36f18683ad4..d4fbd2f7e2e 100644 --- a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json +++ b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json @@ -22,6 +22,7 @@ "editorGroupHeader.tabsBackground": "#131510", "editorLineNumber.activeForeground": "#adadad", "tab.inactiveBackground": "#131510", + "tab.modifiedBorder": "#cd9731", "titleBar.activeBackground": "#423523", "statusBar.background": "#423523", "statusBar.debuggingBackground": "#423523", diff --git a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json index 846b4feae52..b50041d6cdd 100644 --- a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json +++ b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json @@ -23,6 +23,7 @@ "editorGroupHeader.tabsBackground": "#282828", "tab.inactiveBackground": "#404040", "tab.border": "#303030", + "tab.modifiedBorder": "#6796e6", "tab.inactiveForeground": "#d8d8d8", "peekView.border": "#3655b5", "panelTitle.activeForeground": "#ffffff", diff --git a/extensions/theme-monokai/themes/monokai-color-theme.json b/extensions/theme-monokai/themes/monokai-color-theme.json index 8a21859255b..3806508775a 100644 --- a/extensions/theme-monokai/themes/monokai-color-theme.json +++ b/extensions/theme-monokai/themes/monokai-color-theme.json @@ -35,6 +35,7 @@ "editorGroup.dropBackground": "#41433980", "tab.inactiveBackground": "#34352f", "tab.border": "#1e1f1c", + "tab.modifiedBorder": "#007acc", "tab.inactiveForeground": "#ccccc7", // needs to be bright so it's readable when another editor group is focused "widget.shadow": "#000000", "progressBar.background": "#75715E", diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index 5d575484e9c..b1755e55baa 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -497,6 +497,7 @@ "editor.lineHighlightBackground": "#E4F6D4", "editorLineNumber.activeForeground": "#9769dc", "editor.selectionBackground": "#C9D0D9", + "tab.modifiedBorder": "#f1897f", "panel.background": "#F5F5F5", "sideBar.background": "#F2F2F2", "sideBarSectionHeader.background": "#ede8ef", diff --git a/extensions/theme-red/themes/Red-color-theme.json b/extensions/theme-red/themes/Red-color-theme.json index 2231f5e7e4e..3de9575b0db 100644 --- a/extensions/theme-red/themes/Red-color-theme.json +++ b/extensions/theme-red/themes/Red-color-theme.json @@ -5,6 +5,7 @@ "activityBar.background": "#580000", "tab.inactiveBackground": "#300a0a", "tab.activeBackground": "#490000", + "tab.modifiedBorder": "#db7e58", "sideBar.background": "#330000", "statusBar.background": "#700000", "statusBar.noFolderBackground": "#700000", diff --git a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json index e11ed770918..30cbe306f4d 100644 --- a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json +++ b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json @@ -422,6 +422,7 @@ "tab.inactiveForeground": "#93A1A1", "tab.inactiveBackground": "#004052", "tab.border": "#003847", + "tab.modifiedBorder": "#268bd2", // Workbench: Activity Bar "activityBar.background": "#003847", diff --git a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json index 1f0cd3202e7..79caab20000 100644 --- a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json +++ b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json @@ -417,6 +417,7 @@ "tab.activeBackground": "#FDF6E3", "tab.inactiveForeground": "#586E75", "tab.inactiveBackground": "#D3CBB7", + "tab.modifiedBorder": "#cb4b16", // "tab.activeBackground": "", // "tab.activeForeground": "", // "tab.inactiveForeground": "", diff --git a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json index 7ffe04a8d10..8d01980349f 100644 --- a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json +++ b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json @@ -26,6 +26,7 @@ "editorGroup.dropBackground": "#25375daa", "peekViewResult.background": "#001c40", "tab.inactiveBackground": "#001c40", + "tab.modifiedBorder": "#FFEEAD", "debugToolBar.background": "#001c40", "titleBar.activeBackground": "#001126", "statusBar.background": "#001126", diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index f16b7fd8627..8a636e69d9a 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -182,6 +182,7 @@ const configurationValueWhitelist = [ 'workbench.editor.enablePreview', 'workbench.editor.enablePreviewFromQuickOpen', 'workbench.editor.showTabs', + 'workbench.editor.highlightModifiedTabs', 'workbench.editor.swipeToNavigate', 'workbench.sideBar.location', 'workbench.startupEditor', diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index b86023265b4..0cb979f115a 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -28,6 +28,7 @@ export interface IEditorPartOptions extends IWorkbenchEditorPartConfiguration { export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { showTabs: true, + highlightModifiedTabs: false, tabCloseButton: 'right', tabSizing: 'fit', showIcons: true, diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 020706de84f..53dc25ba88e 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -92,28 +92,33 @@ display: none; /* hidden by default until a color is provided (see below) */ } -.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container { +.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container, +.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container, +.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container { display: block; position: absolute; - top: 0; left: 0; z-index: 6; /* over possible title border */ pointer-events: none; - background-color: var(--tab-border-top-color); width: 100%; +} + +.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container { + top: 0; height: 1px; + background-color: var(--tab-border-top-color); } .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container { - display: block; - position: absolute; bottom: 0; - left: 0; - z-index: 6; /* over possible title border */ - pointer-events: none; - background-color: var(--tab-border-bottom-color); - width: 100%; height: 1px; + background-color: var(--tab-border-bottom-color); +} + +.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container { + top: 0; + height: 2px; + background-color: var(--tab-dirty-border-top-color); } /* Tab Label */ diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 0b796ad956b..b6626ac1bbf 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -25,7 +25,7 @@ import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElemen import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP } from 'vs/workbench/common/theme'; +import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry'; import { ResourcesDropHandler, fillResourceDataTransfers, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; @@ -340,6 +340,9 @@ export class TabsTitleControl extends TitleControl { // Activity has an impact on each tab this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel) => { this.redrawEditorActive(isGroupActive, editor, tabContainer, tabLabelWidget); + if (this.accessor.partOptions.highlightModifiedTabs) { + this.redrawEditorDirty(editor, tabContainer); + } }); // Activity has an impact on the toolbar, so we need to update and layout @@ -378,7 +381,8 @@ export class TabsTitleControl extends TitleControl { oldOptions.tabCloseButton !== newOptions.tabCloseButton || oldOptions.tabSizing !== newOptions.tabSizing || oldOptions.showIcons !== newOptions.showIcons || - oldOptions.iconTheme !== newOptions.iconTheme + oldOptions.iconTheme !== newOptions.iconTheme || + oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs ) { this.redraw(); } @@ -892,7 +896,7 @@ export class TabsTitleControl extends TitleControl { // Tab is inactive else { - // Containr + // Container removeClass(tabContainer, 'active'); tabContainer.setAttribute('aria-selected', 'false'); tabContainer.style.backgroundColor = this.getColor(TAB_INACTIVE_BACKGROUND); @@ -904,10 +908,43 @@ export class TabsTitleControl extends TitleControl { } private redrawEditorDirty(editor: IEditorInput, tabContainer: HTMLElement): void { + + // Tab: dirty if (editor.isDirty()) { addClass(tabContainer, 'dirty'); - } else { + + // Highlight modified tabs with a border if configured + if (this.accessor.partOptions.highlightModifiedTabs) { + const isGroupActive = this.accessor.activeGroup === this.group; + const isTabActive = this.group.isActive(editor); + + let modifiedBorderColor: string; + if (isGroupActive && isTabActive) { + modifiedBorderColor = this.getColor(TAB_ACTIVE_MODIFIED_BORDER); + } else if (isGroupActive && !isTabActive) { + modifiedBorderColor = this.getColor(TAB_INACTIVE_MODIFIED_BORDER); + } else if (!isGroupActive && isTabActive) { + modifiedBorderColor = this.getColor(TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER); + } else { + modifiedBorderColor = this.getColor(TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER); + } + + if (modifiedBorderColor) { + addClass(tabContainer, 'dirty-border-top'); + tabContainer.style.setProperty('--tab-dirty-border-top-color', modifiedBorderColor); + } + } else { + removeClass(tabContainer, 'dirty-border-top'); + tabContainer.style.removeProperty('--tab-dirty-border-top-color'); + } + } + + // Tab: not dirty + else { removeClass(tabContainer, 'dirty'); + + removeClass(tabContainer, 'dirty-border-top'); + tabContainer.style.removeProperty('--tab-dirty-border-top-color'); } } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index c179bc95c23..9e02f137ee7 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -937,6 +937,7 @@ export interface IWorkbenchEditorConfiguration { export interface IWorkbenchEditorPartConfiguration { showTabs?: boolean; + highlightModifiedTabs?: boolean; tabCloseButton?: 'left' | 'right' | 'off'; tabSizing?: 'fit' | 'shrink'; showIcons?: boolean; diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 1f6ab7f0dde..d07022f7003 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -60,24 +60,48 @@ export const TAB_ACTIVE_BORDER = registerColor('tab.activeBorder', { hc: null }, nls.localize('tabActiveBorder', "Border on the bottom of an active tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); -export const TAB_ACTIVE_BORDER_TOP = registerColor('tab.activeBorderTop', { - dark: null, - light: null, - hc: null -}, nls.localize('tabActiveBorderTop', "Border to the top of an active tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); - export const TAB_UNFOCUSED_ACTIVE_BORDER = registerColor('tab.unfocusedActiveBorder', { dark: transparent(TAB_ACTIVE_BORDER, 0.5), light: transparent(TAB_ACTIVE_BORDER, 0.7), hc: null }, nls.localize('tabActiveUnfocusedBorder', "Border on the bottom of an active tab in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_ACTIVE_BORDER_TOP = registerColor('tab.activeBorderTop', { + dark: null, + light: null, + hc: null +}, nls.localize('tabActiveBorderTop', "Border to the top of an active tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + export const TAB_UNFOCUSED_ACTIVE_BORDER_TOP = registerColor('tab.unfocusedActiveBorderTop', { dark: transparent(TAB_ACTIVE_BORDER_TOP, 0.5), light: transparent(TAB_ACTIVE_BORDER_TOP, 0.7), hc: null }, nls.localize('tabActiveUnfocusedBorderTop', "Border to the top of an active tab in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_ACTIVE_MODIFIED_BORDER = registerColor('tab.activeModifiedBorder', { + dark: '#3399CC', + light: '#33AAEE', + hc: null +}, nls.localize('tabActiveModifiedBorder', "Border on the top of modified (dirty) active tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + +export const TAB_INACTIVE_MODIFIED_BORDER = registerColor('tab.inactiveModifiedBorder', { + dark: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5), + light: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5), + hc: Color.white +}, nls.localize('tabInactiveModifiedBorder', "Border on the top of modified (dirty) inactive tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + +export const TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER = registerColor('tab.unfocusedActiveModifiedBorder', { + dark: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5), + light: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.7), + hc: Color.white +}, nls.localize('unfocusedActiveModifiedBorder', "Border on the top of modified (dirty) active tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + +export const TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER = registerColor('tab.unfocusedInactiveModifiedBorder', { + dark: transparent(TAB_INACTIVE_MODIFIED_BORDER, 0.5), + light: transparent(TAB_INACTIVE_MODIFIED_BORDER, 0.5), + hc: Color.white +}, nls.localize('unfocusedINactiveModifiedBorder', "Border on the top of modified (dirty) inactive tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + export const TAB_HOVER_BORDER = registerColor('tab.hoverBorder', { dark: null, light: null, diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index f775a01626f..5aeae1bba94 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -478,6 +478,11 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), 'default': true }, + 'workbench.editor.highlightModifiedTabs': { + 'type': 'boolean', + 'description': nls.localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not."), + 'default': false + }, 'workbench.editor.labelFormat': { 'type': 'string', 'enum': ['default', 'short', 'medium', 'long'],