diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index a3483f451e2effdb25029d37f44e6538d2591796..3ae1ace01c1d6af8cc1fc5de9a966664b721f7cc 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -1,7 +1,7 @@ [ { "name": "ms-vscode.node-debug", - "version": "1.35.3", + "version": "1.38.2", "repo": "https://github.com/Microsoft/vscode-node-debug", "metadata": { "id": "b6ded8fb-a0a0-4c1c-acbd-ab2a3bc995a6", diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 7320c08eaead34ff7760f283439367c33fe172c6..40b31dff92312ff2bdca078b010110e08420fecc 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -679,12 +679,15 @@ export class Repository implements Disposable { const root = Uri.file(repository.root); this._sourceControl = scm.createSourceControl('git', 'Git', root); - this._sourceControl.inputBox.placeholder = localize('commitMessage', "Message (press {0} to commit)"); + this._sourceControl.acceptInputCommand = { command: 'git.commit', title: localize('commit', "Commit"), arguments: [this._sourceControl] }; this._sourceControl.quickDiffProvider = this; this._sourceControl.inputBox.validateInput = this.validateInput.bind(this); this.disposables.push(this._sourceControl); + this.updateInputBoxPlaceholder(); + this.disposables.push(this.onDidRunGitStatus(() => this.updateInputBoxPlaceholder())); + this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "MERGE CHANGES")); this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "STAGED CHANGES")); this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "CHANGES")); @@ -1649,6 +1652,21 @@ export class Repository implements Disposable { return `${this.HEAD.behind}↓ ${this.HEAD.ahead}↑`; } + private updateInputBoxPlaceholder(): void { + const HEAD = this.HEAD; + + if (HEAD) { + const tag = this.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0]; + const tagName = tag && tag.name; + const head = HEAD.name || tagName || (HEAD.commit || '').substr(0, 8); + + // '{0}' will be replaced by the corresponding key-command later in the process, which is why it needs to stay. + this._sourceControl.inputBox.placeholder = localize('commitMessageWithHeadLabel', "Message ({0} to commit on '{1}')", "{0}", head); + } else { + this._sourceControl.inputBox.placeholder = localize('commitMessage', "Message ({0} to commit)"); + } + } + dispose(): void { this.disposables = dispose(this.disposables); } diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index 5291e7ab716a073d3ff80e25d5198d86ae53f83a..0905e90acb1ead870fc6fe88870f11866b0388c8 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -337,14 +337,15 @@ suite('window namespace tests', () => { }); terminal.dispose(); }); - overrideDimensionsEmitter.fire({ columns: 10, rows: 5 }); }); const writeEmitter = new EventEmitter(); const overrideDimensionsEmitter = new EventEmitter(); const pty: Pseudoterminal = { onDidWrite: writeEmitter.event, onDidOverrideDimensions: overrideDimensionsEmitter.event, - open: () => {}, + open: () => { + overrideDimensionsEmitter.fire({ columns: 10, rows: 5 }); + }, close: () => {} }; const terminal = window.createTerminal({ name: 'foo', pty }); diff --git a/src/vs/base/browser/ui/checkbox/check-dark.svg b/src/vs/base/browser/ui/checkbox/check-dark.svg new file mode 100644 index 0000000000000000000000000000000000000000..865cc83c347afec742f3873f3ee9e0dc8a0198ef --- /dev/null +++ b/src/vs/base/browser/ui/checkbox/check-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/base/browser/ui/checkbox/check-light.svg b/src/vs/base/browser/ui/checkbox/check-light.svg new file mode 100644 index 0000000000000000000000000000000000000000..e1a546660ed155af42538d288d2e3e0ab197da67 --- /dev/null +++ b/src/vs/base/browser/ui/checkbox/check-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/base/browser/ui/checkbox/checkbox.css b/src/vs/base/browser/ui/checkbox/checkbox.css index 65f1dcd2318d5436c0fc4fe13d40b0807a10460a..26f48b88c0225de9350121b7d02b23c4e2353e62 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.css +++ b/src/vs/base/browser/ui/checkbox/checkbox.css @@ -39,4 +39,24 @@ .hc-black .monaco-custom-checkbox:hover { background: none; -} \ No newline at end of file +} + +.monaco-custom-checkbox.monaco-simple-checkbox { + height: 18px; + width: 18px; + border: 1px solid transparent; + border-radius: 3px; + margin-right: 9px; + margin-left: 0px; + padding: 0px; + opacity: 1; + background-size: 16px !important; +} + +.monaco-custom-checkbox.monaco-simple-checkbox.checked { + background: url('check-light.svg') center center no-repeat; +} + +.monaco-custom-checkbox.monaco-simple-checkbox.checked { + background: url('check-dark.svg') center center no-repeat; +} diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index ed0bcfa0ab15e6e733d454c4e13b0f21e87515e3..f5fe47cdd855aaea0869bf12d7c09ca732569e66 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -25,6 +25,12 @@ export interface ICheckboxStyles { inputActiveOptionBackground?: Color; } +export interface ISimpleCheckboxStyles { + checkboxBackground?: Color; + checkboxBorder?: Color; + checkboxForeground?: Color; +} + const defaultOpts = { inputActiveOptionBorder: Color.fromHex('#007ACC00'), inputActiveOptionBackground: Color.fromHex('#0E639C50') @@ -174,3 +180,46 @@ export class Checkbox extends Widget { this.domNode.setAttribute('aria-disabled', String(true)); } } + +export class SimpleCheckbox extends Widget { + private checkbox: Checkbox; + private styles: ISimpleCheckboxStyles; + + readonly domNode: HTMLElement; + + constructor(private title: string, private isChecked: boolean) { + super(); + + this.checkbox = new Checkbox({ title: this.title, isChecked: this.isChecked, actionClassName: 'monaco-simple-checkbox' }); + + this.domNode = this.checkbox.domNode; + + this.styles = {}; + + this.checkbox.onChange(() => { + this.applyStyles(); + }); + } + + get checked(): boolean { + return this.checkbox.checked; + } + + set checked(newIsChecked: boolean) { + this.checkbox.checked = newIsChecked; + + this.applyStyles(); + } + + style(styles: ISimpleCheckboxStyles): void { + this.styles = styles; + + this.applyStyles(); + } + + protected applyStyles(): void { + this.domNode.style.color = this.styles.checkboxForeground ? this.styles.checkboxForeground.toString() : null; + this.domNode.style.backgroundColor = this.styles.checkboxBackground ? this.styles.checkboxBackground.toString() : null; + this.domNode.style.borderColor = this.styles.checkboxBorder ? this.styles.checkboxBorder.toString() : null; + } +} diff --git a/src/vs/base/browser/ui/dialog/dialog.css b/src/vs/base/browser/ui/dialog/dialog.css index e05ed90a949eade1821d562a24e483483fbba726..83d8d7eca669a5429df8ae7b8c2d8a0232cf4458 100644 --- a/src/vs/base/browser/ui/dialog/dialog.css +++ b/src/vs/base/browser/ui/dialog/dialog.css @@ -149,6 +149,11 @@ outline-style: solid; } +.monaco-workbench .dialog-box .dialog-message-row .dialog-message-container .dialog-checkbox-row { + padding: 15px 0px 0px; + display: flex; +} + /** Dialog: Buttons Row */ .monaco-workbench .dialog-box > .dialog-buttons-row { display: flex; @@ -175,4 +180,4 @@ margin: 4px 5px; /* allows button focus outline to be visible */ overflow: hidden; text-overflow: ellipsis; -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 2b5d836146d46f73b4167ac1b328be218c6ba8ba..f6b707d975d70159e9bd9efa270b9f6d80dd73d8 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -16,15 +16,23 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { isMacintosh, isLinux } from 'vs/base/common/platform'; +import { SimpleCheckbox, ISimpleCheckboxStyles } from 'vs/base/browser/ui/checkbox/checkbox'; export interface IDialogOptions { cancelId?: number; detail?: string; + checkboxLabel?: string; + checkboxChecked?: boolean; type?: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending'; keyEventProcessor?: (event: StandardKeyboardEvent) => void; } -export interface IDialogStyles extends IButtonStyles { +export interface IDialogResult { + button: number; + checkboxChecked?: boolean; +} + +export interface IDialogStyles extends IButtonStyles, ISimpleCheckboxStyles { dialogForeground?: Color; dialogBackground?: Color; dialogShadow?: Color; @@ -42,6 +50,7 @@ export class Dialog extends Disposable { private buttonsContainer: HTMLElement | undefined; private messageDetailElement: HTMLElement | undefined; private iconElement: HTMLElement | undefined; + private checkbox: SimpleCheckbox | undefined; private toolbarContainer: HTMLElement | undefined; private buttonGroup: ButtonGroup | undefined; private styles: IDialogStyles | undefined; @@ -68,6 +77,19 @@ export class Dialog extends Disposable { this.messageDetailElement = messageContainer.appendChild($('.dialog-message-detail')); this.messageDetailElement.innerText = this.options.detail ? this.options.detail : message; + if (this.options.checkboxLabel) { + const checkboxRowElement = messageContainer.appendChild($('.dialog-checkbox-row')); + + this.checkbox = this._register(new SimpleCheckbox(this.options.checkboxLabel, !!this.options.checkboxChecked)); + + checkboxRowElement.appendChild(this.checkbox.domNode); + + const checkboxMessageElement = checkboxRowElement.appendChild($('.dialog-checkbox-message')); + checkboxMessageElement.innerText = this.options.checkboxLabel; + } + + + const toolbarRowElement = this.element.appendChild($('.dialog-toolbar-row')); this.toolbarContainer = toolbarRowElement.appendChild($('.dialog-toolbar')); } @@ -78,12 +100,12 @@ export class Dialog extends Disposable { } } - async show(): Promise { + async show(): Promise { this.focusToReturn = document.activeElement as HTMLElement; - return new Promise((resolve) => { + return new Promise((resolve) => { if (!this.element || !this.buttonsContainer || !this.iconElement || !this.toolbarContainer) { - resolve(0); + resolve({ button: 0 }); return; } @@ -112,7 +134,7 @@ export class Dialog extends Disposable { this._register(button.onDidClick(e => { EventHelper.stop(e); - resolve(buttonMap[index].index); + resolve({ button: buttonMap[index].index, checkboxChecked: this.checkbox ? this.checkbox.checked : undefined }); })); }); @@ -147,7 +169,7 @@ export class Dialog extends Disposable { const evt = new StandardKeyboardEvent(e); if (evt.equals(KeyCode.Escape)) { - resolve(this.options.cancelId || 0); + resolve({ button: this.options.cancelId || 0, checkboxChecked: this.checkbox ? this.checkbox.checked : undefined }); } })); @@ -187,7 +209,7 @@ export class Dialog extends Disposable { const actionBar = new ActionBar(this.toolbarContainer, {}); const action = new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), 'dialog-close-action', true, () => { - resolve(this.options.cancelId || 0); + resolve({ button: this.options.cancelId || 0, checkboxChecked: this.checkbox ? this.checkbox.checked : undefined }); return Promise.resolve(); }); @@ -221,6 +243,10 @@ export class Dialog extends Disposable { if (this.buttonGroup) { this.buttonGroup.buttons.forEach(button => button.style(style)); } + + if (this.checkbox) { + this.checkbox.style(style); + } } } } @@ -261,4 +287,4 @@ export class Dialog extends Disposable { return buttonMap; } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 497678cee416dccebf88e7030034acef9bbee46f..ae8c4ed810a355b97374550d63a93d035fc75ece 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -348,6 +348,7 @@ class BranchNode implements ISplitView, IDisposable { } this.splitview.setViewVisible(index, visible); + this._onDidChange.fire(undefined); } getChildCachedVisibleSize(index: number): number | undefined { diff --git a/src/vs/base/browser/ui/splitview/splitview.css b/src/vs/base/browser/ui/splitview/splitview.css index 6fb8f1c61d0e3cf871be11981a5563b14cbaf04e..c0f3634d499f177d0ded51007de94d127d77f3fd 100644 --- a/src/vs/base/browser/ui/splitview/splitview.css +++ b/src/vs/base/browser/ui/splitview/splitview.css @@ -39,6 +39,7 @@ white-space: initial; flex: none; position: relative; + overflow: hidden; } .monaco-split-view2 > .split-view-container > .split-view-view:not(.visible) { @@ -72,4 +73,4 @@ .monaco-split-view2.separator-border.vertical > .split-view-container > .split-view-view:not(:first-child)::before { height: 1px; width: 100%; -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 37d44dbaad9b0d7e9e325cfae669588f42ad43b0..5f6cbe73e3e230f95b0baf35da3fdf2066c1cd2d 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -125,7 +125,10 @@ abstract class ViewItem { dom.addClass(container, 'visible'); } - abstract layout(): void; + layout(): void { + this.container.scrollTop = 0; + this.container.scrollLeft = 0; + } layoutView(orientation: Orientation): void { this.view.layout(this.size, orientation); @@ -140,6 +143,7 @@ abstract class ViewItem { class VerticalViewItem extends ViewItem { layout(): void { + super.layout(); this.container.style.height = `${this.size}px`; this.layoutView(Orientation.VERTICAL); } @@ -148,6 +152,7 @@ class VerticalViewItem extends ViewItem { class HorizontalViewItem extends ViewItem { layout(): void { + super.layout(); this.container.style.width = `${this.size}px`; this.layoutView(Orientation.HORIZONTAL); } diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index 9b85e624b524b8298863fdda2add1f2035584204..580311d76911778e4b1685e560a426d122be837d 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -95,7 +95,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this._typicalHalfwidthCharacterWidth = conf.editor.fontInfo.typicalHalfwidthCharacterWidth; this._isViewportWrapping = conf.editor.wrappingInfo.isViewportWrapping; this._revealHorizontalRightPadding = conf.editor.viewInfo.revealHorizontalRightPadding; - this._scrollOff = conf.editor.viewInfo.scrollOff; + this._scrollOff = conf.editor.viewInfo.cursorSurroundingLines; this._canUseLayerHinting = conf.editor.canUseLayerHinting; this._viewLineOptions = new ViewLineOptions(conf, this._context.theme.type); @@ -152,7 +152,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, } if (e.viewInfo) { this._revealHorizontalRightPadding = conf.editor.viewInfo.revealHorizontalRightPadding; - this._scrollOff = conf.editor.viewInfo.scrollOff; + this._scrollOff = conf.editor.viewInfo.cursorSurroundingLines; } if (e.canUseLayerHinting) { this._canUseLayerHinting = conf.editor.canUseLayerHinting; diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 45f0a760a3c7213afdac7b9ba6d2011967b863f7..65f579a296e4ccaa7eb6bf7d0730adf4073fe765 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -268,10 +268,10 @@ const editorConfiguration: IConfigurationNode = { 'default': 'on', 'description': nls.localize('lineNumbers', "Controls the display of line numbers.") }, - 'editor.scrollOff': { + 'editor.cursorSurroundingLines': { 'type': 'number', - 'default': EDITOR_DEFAULTS.viewInfo.scrollOff, - 'description': nls.localize('scrollOff', "Controls the number of context lines above and below the cursor.") + 'default': EDITOR_DEFAULTS.viewInfo.cursorSurroundingLines, + 'description': nls.localize('cursorSurroundingLines', "Controls the minimal number of visible leading and trailing lines surrounding the cursor. Known as 'scrollOff' or `scrollOffset` in some other editors.") }, 'editor.renderFinalNewline': { 'type': 'boolean', diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 38d27f4d44b8d0a7d2490e8cc54e3e5609523e6b..71b0cbb884391ad556626b872e0091239e841540 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -269,10 +269,10 @@ export interface IEditorOptions { */ lineNumbers?: 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); /** - * Controls the number of context lines above and below the cursor. + * Controls the minimal number of visible leading and trailing lines surrounding the cursor. * Defaults to 0. */ - scrollOff?: number; + cursorSurroundingLines?: number; /** * Render last line number when the file ends with a newline. * Defaults to true. @@ -987,7 +987,7 @@ export interface InternalEditorViewOptions { readonly ariaLabel: string; readonly renderLineNumbers: RenderLineNumbersType; readonly renderCustomLineNumbers: ((lineNumber: number) => string) | null; - readonly scrollOff: number; + readonly cursorSurroundingLines: number; readonly renderFinalNewline: boolean; readonly selectOnLineNumbers: boolean; readonly glyphMargin: boolean; @@ -1296,7 +1296,7 @@ export class InternalEditorOptions { && a.ariaLabel === b.ariaLabel && a.renderLineNumbers === b.renderLineNumbers && a.renderCustomLineNumbers === b.renderCustomLineNumbers - && a.scrollOff === b.scrollOff + && a.cursorSurroundingLines === b.cursorSurroundingLines && a.renderFinalNewline === b.renderFinalNewline && a.selectOnLineNumbers === b.selectOnLineNumbers && a.glyphMargin === b.glyphMargin @@ -2056,7 +2056,7 @@ export class EditorOptionsValidator { disableMonospaceOptimizations: disableMonospaceOptimizations, rulers: rulers, ariaLabel: _string(opts.ariaLabel, defaults.ariaLabel), - scrollOff: _clampedInt(opts.scrollOff, defaults.cursorWidth, 0, Number.MAX_VALUE), + cursorSurroundingLines: _clampedInt(opts.cursorSurroundingLines, defaults.cursorWidth, 0, Number.MAX_VALUE), renderLineNumbers: renderLineNumbers, renderCustomLineNumbers: renderCustomLineNumbers, renderFinalNewline: _boolean(opts.renderFinalNewline, defaults.renderFinalNewline), @@ -2180,7 +2180,7 @@ export class InternalEditorOptionsFactory { ariaLabel: (accessibilityIsOff ? nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. Press Alt+F1 for options.") : opts.viewInfo.ariaLabel), renderLineNumbers: opts.viewInfo.renderLineNumbers, renderCustomLineNumbers: opts.viewInfo.renderCustomLineNumbers, - scrollOff: opts.viewInfo.scrollOff, + cursorSurroundingLines: opts.viewInfo.cursorSurroundingLines, renderFinalNewline: opts.viewInfo.renderFinalNewline, selectOnLineNumbers: opts.viewInfo.selectOnLineNumbers, glyphMargin: opts.viewInfo.glyphMargin, @@ -2645,7 +2645,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { ariaLabel: nls.localize('editorViewAccessibleLabel', "Editor content"), renderLineNumbers: RenderLineNumbersType.On, renderCustomLineNumbers: null, - scrollOff: 0, + cursorSurroundingLines: 0, renderFinalNewline: true, selectOnLineNumbers: true, glyphMargin: true, diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 826d8d9aa36afff294d3161b4dc8b59362c4c422..e0ac791724bc567d00396d800959d2b5ae74183b 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -961,12 +961,14 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate string); /** - * Controls the number of context lines above and below the cursor. + * Controls the minimal number of visible leading and trailing lines surrounding the cursor. * Defaults to 0. */ - scrollOff?: number; + cursorSurroundingLines?: number; /** * Render last line number when the file ends with a newline. * Defaults to true. @@ -3302,7 +3302,7 @@ declare namespace monaco.editor { readonly ariaLabel: string; readonly renderLineNumbers: RenderLineNumbersType; readonly renderCustomLineNumbers: ((lineNumber: number) => string) | null; - readonly scrollOff: number; + readonly cursorSurroundingLines: number; readonly renderFinalNewline: boolean; readonly selectOnLineNumbers: boolean; readonly glyphMargin: boolean; diff --git a/src/vs/platform/dialogs/browser/dialogService.ts b/src/vs/platform/dialogs/browser/dialogService.ts index 5a906768029b29cfa9f7ecf5b04b26cbdc452b2a..347e242dd6d66710b59abc27735c448b9c8d1c51 100644 --- a/src/vs/platform/dialogs/browser/dialogService.ts +++ b/src/vs/platform/dialogs/browser/dialogService.ts @@ -40,32 +40,35 @@ export class DialogService implements IDialogService { buttons.push(nls.localize('cancelButton', "Cancel")); } - const severity = this.getSeverity(confirmation.type || 'none'); - const result = await this.show(severity, confirmation.message, buttons, { cancelId: 1, detail: confirmation.detail }); + const dialogDisposables = new DisposableStore(); + const dialog = new Dialog( + this.layoutService.container, + confirmation.message, + buttons, + { + detail: confirmation.detail, + cancelId: 1, + type: confirmation.type, + keyEventProcessor: (event: StandardKeyboardEvent) => { + EventHelper.stop(event, true); + }, + checkboxChecked: confirmation.checkbox ? confirmation.checkbox.checked : undefined, + checkboxLabel: confirmation.checkbox ? confirmation.checkbox.label : undefined + }); - return { confirmed: result === 0 }; - } + dialogDisposables.add(dialog); + dialogDisposables.add(attachDialogStyler(dialog, this.themeService)); - private getSeverity(type: DialogType): Severity { - switch (type) { - case 'error': - return Severity.Error; - case 'warning': - return Severity.Warning; - case 'question': - case 'info': - return Severity.Info; - case 'none': - default: - return Severity.Ignore; - } + const result = await dialog.show(); + dialogDisposables.dispose(); + + return { confirmed: result.button === 0, checkboxChecked: result.checkboxChecked }; } private getDialogType(severity: Severity): DialogType { return (severity === Severity.Info) ? 'question' : (severity === Severity.Error) ? 'error' : (severity === Severity.Warning) ? 'warning' : 'none'; } - async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { this.logService.trace('DialogService#show', message); @@ -86,9 +89,9 @@ export class DialogService implements IDialogService { dialogDisposables.add(dialog); dialogDisposables.add(attachDialogStyler(dialog, this.themeService)); - const choice = await dialog.show(); + const result = await dialog.show(); dialogDisposables.dispose(); - return choice; + return result.button; } } diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index ea57ce912334420fe387289da555cd50f8904100..6999633d9f0b87e9bfd662c7288b01f17cf46554 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -127,6 +127,8 @@ export const IDialogService = createDecorator('dialogService'); export interface IDialogOptions { cancelId?: number; detail?: string; + checkboxLabel?: string; + checkboxChecked?: boolean; } /** @@ -234,4 +236,4 @@ export function getConfirmMessage(start: string, resourcesToConfirm: URI[]): str message.push(''); return message.join('\n'); -} \ No newline at end of file +} diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 6f356bfe66ba810f7eab75af9a97a5b940906f67..47b428fbb8406cdb3c7c7a150681b2e7f1653dc7 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -222,6 +222,10 @@ export const selectListBackground = registerColor('dropdown.listBackground', { d export const selectForeground = registerColor('dropdown.foreground', { dark: '#F0F0F0', light: null, hc: Color.white }, nls.localize('dropdownForeground', "Dropdown foreground.")); export const selectBorder = registerColor('dropdown.border', { dark: selectBackground, light: '#CECECE', hc: contrastBorder }, nls.localize('dropdownBorder', "Dropdown border.")); +export const simpleCheckboxBackground = registerColor('checkbox.background', { dark: selectBackground, light: selectBackground, hc: selectBackground }, nls.localize('checkbox.background', "Background color of checkbox widget.")); +export const simpleCheckboxForeground = registerColor('checkbox.foreground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, nls.localize('checkbox.foreground', "Foreground color of checkbox widget.")); +export const simpleCheckboxBorder = registerColor('checkbox.border', { dark: selectBorder, light: selectBorder, hc: selectBorder }, nls.localize('checkbox.border', "Border color of checkbox widget.")); + export const listFocusBackground = registerColor('list.focusBackground', { dark: '#062F4A', light: '#D6EBFF', hc: null }, nls.localize('listFocusBackground', "List/Tree background color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listFocusForeground = registerColor('list.focusForeground', { dark: null, light: null, hc: null }, nls.localize('listFocusForeground', "List/Tree foreground color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listActiveSelectionBackground = registerColor('list.activeSelectionBackground', { dark: '#094771', light: '#0074E8', hc: null }, nls.localize('listActiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index c1732ca17357b952b340dc41d2a444dc20034c9b..40de3567375420f8033516c6e5337e9c2da022c2 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry'; +import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectListBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, inputActiveOptionBackground, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, badgeBackground, badgeForeground, progressBarBackground, breadcrumbsForeground, breadcrumbsFocusForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, editorWidgetBorder, inputValidationInfoForeground, inputValidationWarningForeground, inputValidationErrorForeground, menuForeground, menuBackground, menuSelectionForeground, menuSelectionBackground, menuSelectionBorder, menuBorder, menuSeparatorBackground, darken, listFilterWidgetOutline, listFilterWidgetNoMatchesOutline, listFilterWidgetBackground, editorWidgetBackground, treeIndentGuidesStroke, editorWidgetForeground, simpleCheckboxBackground, simpleCheckboxBorder, simpleCheckboxForeground } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; @@ -341,6 +341,9 @@ export interface IDialogStyleOverrides extends IButtonStyleOverrides { dialogBackground?: ColorIdentifier; dialogShadow?: ColorIdentifier; dialogBorder?: ColorIdentifier; + checkboxBorder?: ColorIdentifier; + checkboxBackground?: ColorIdentifier; + checkboxForeground?: ColorIdentifier; } export const defaultDialogStyles = { @@ -351,7 +354,10 @@ export const defaultDialogStyles = { buttonForeground: buttonForeground, buttonBackground: buttonBackground, buttonHoverBackground: buttonHoverBackground, - buttonBorder: contrastBorder + buttonBorder: contrastBorder, + checkboxBorder: simpleCheckboxBorder, + checkboxBackground: simpleCheckboxBackground, + checkboxForeground: simpleCheckboxForeground }; diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index b1de1c5bdc6facdc1063561d9d1715886c87f774..10bb2639e9ed86d5bfe29d66b9206aac5bae193c 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; -import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext } from 'vs/workbench/common/editor'; +import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -52,17 +52,20 @@ export class WorkbenchContextKeysHandler extends Disposable { private inputFocusedContext: IContextKey; private activeEditorContext: IContextKey; + + private activeEditorGroupEmpty: IContextKey; + private activeEditorGroupIndex: IContextKey; + private activeEditorGroupLast: IContextKey; + private multipleEditorGroupsContext: IContextKey; + private editorsVisibleContext: IContextKey; private textCompareEditorVisibleContext: IContextKey; private textCompareEditorActiveContext: IContextKey; - private activeEditorGroupEmpty: IContextKey; - private multipleEditorGroupsContext: IContextKey; private splitEditorsVerticallyContext: IContextKey; private workbenchStateContext: IContextKey; private workspaceFolderCountContext: IContextKey; - private inZenModeContext: IContextKey; private isFullscreenContext: IContextKey; private isCenteredLayoutContext: IContextKey; @@ -90,8 +93,10 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(this.editorService.onDidActiveEditorChange(() => this.updateEditorContextKeys())); this._register(this.editorService.onDidVisibleEditorsChange(() => this.updateEditorContextKeys())); + this._register(this.editorGroupService.onDidAddGroup(() => this.updateEditorContextKeys())); this._register(this.editorGroupService.onDidRemoveGroup(() => this.updateEditorContextKeys())); + this._register(this.editorGroupService.onDidGroupIndexChange(() => this.updateEditorContextKeys())); this._register(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(), true)); @@ -141,6 +146,8 @@ export class WorkbenchContextKeysHandler extends Disposable { this.textCompareEditorVisibleContext = TextCompareEditorVisibleContext.bindTo(this.contextKeyService); this.textCompareEditorActiveContext = TextCompareEditorActiveContext.bindTo(this.contextKeyService); this.activeEditorGroupEmpty = ActiveEditorGroupEmptyContext.bindTo(this.contextKeyService); + this.activeEditorGroupIndex = ActiveEditorGroupIndexContext.bindTo(this.contextKeyService); + this.activeEditorGroupLast = ActiveEditorGroupLastContext.bindTo(this.contextKeyService); this.multipleEditorGroupsContext = MultipleEditorGroupsContext.bindTo(this.contextKeyService); // Inputs @@ -176,6 +183,7 @@ export class WorkbenchContextKeysHandler extends Disposable { } private updateEditorContextKeys(): void { + const activeGroup = this.editorGroupService.activeGroup; const activeControl = this.editorService.activeControl; const visibleEditors = this.editorService.visibleControls; @@ -194,12 +202,16 @@ export class WorkbenchContextKeysHandler extends Disposable { this.activeEditorGroupEmpty.reset(); } - if (this.editorGroupService.count > 1) { + const groupCount = this.editorGroupService.count; + if (groupCount > 1) { this.multipleEditorGroupsContext.set(true); } else { this.multipleEditorGroupsContext.reset(); } + this.activeEditorGroupIndex.set(activeGroup.index); + this.activeEditorGroupLast.set(activeGroup.index === groupCount - 1); + if (activeControl) { this.activeEditorContext.set(activeControl.getId()); } else { @@ -250,4 +262,4 @@ export class WorkbenchContextKeysHandler extends Disposable { private updateSideBarContextKeys(): void { this.sideBarVisibleContext.set(this.layoutService.isVisible(Parts.SIDEBAR_PART)); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index d928b4a6c6c666df40807f1700b03b19d27eff75..7f82e7e7562a378eaefc4e8f86133a68180f9a3d 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -1076,33 +1076,15 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi toggleMaximizedPanel(): void { if (this.workbenchGrid instanceof Grid) { - const curSize = this.workbenchGrid.getViewSize(this.panelPartView); - const size = { ...curSize }; - + const size = this.workbenchGrid.getViewSize(this.panelPartView); if (!this.isPanelMaximized()) { - if (this.state.panel.position === Position.BOTTOM) { - size.height = this.panelPartView.maximumHeight; - this.state.panel.sizeBeforeMaximize = curSize.height; - } else { - size.width = this.panelPartView.maximumWidth; - this.state.panel.sizeBeforeMaximize = curSize.width; - } - + this.state.panel.sizeBeforeMaximize = this.state.panel.position === Position.BOTTOM ? size.height : size.width; this.storageService.store(Storage.PANEL_SIZE_BEFORE_MAXIMIZED, this.state.panel.sizeBeforeMaximize, StorageScope.GLOBAL); + this.setEditorHidden(true); } else { - if (this.state.panel.position === Position.BOTTOM) { - size.height = this.state.panel.sizeBeforeMaximize; - } else { - size.width = this.state.panel.sizeBeforeMaximize; - } - - // Unhide the editor if needed - if (this.state.editor.hidden) { - this.setEditorHidden(false); - } + this.setEditorHidden(false); + this.workbenchGrid.resizeView(this.panelPartView, { width: this.state.panel.position === Position.BOTTOM ? size.width : this.state.panel.sizeBeforeMaximize, height: this.state.panel.position === Position.BOTTOM ? this.state.panel.sizeBeforeMaximize : size.height }); } - - this.workbenchGrid.resizeView(this.panelPartView, size); } else { this.workbenchGrid.layout({ toggleMaximizedPanel: true, source: Parts.PANEL_PART }); } @@ -1114,16 +1096,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } if (this.workbenchGrid instanceof Grid) { - try { - // The panel is maximum when the editor is minimum - if (this.state.panel.position === Position.BOTTOM) { - return this.workbenchGrid.getViewSize(this.editorPartView).height <= this.editorPartView.minimumHeight; - } else { - return this.workbenchGrid.getViewSize(this.editorPartView).width <= this.editorPartView.minimumWidth; - } - } catch (e) { - return false; - } + return this.state.editor.hidden; } else { return this.workbenchGrid.isPanelMaximized(); } @@ -1258,7 +1231,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi { type: 'leaf', data: { type: Parts.TITLEBAR_PART }, - size: titleBarHeight + size: titleBarHeight, + visible: this.isVisible(Parts.TITLEBAR_PART) }, { type: 'branch', diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 1408ae24e0e360bd1e0a5f2605ea556024e70b47..f36234ec44106ca39ca6b69ee17b14043eb21b8b 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -118,7 +118,9 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito isEmpty(): boolean; setActive(isActive: boolean): void; - setLabel(label: string): void; + + notifyIndexChanged(newIndex: number): void; + relayout(): void; } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 54be65b6f03376c455aa00bd6303b9ed3872ee61..6f575845305d599749710181c77cb8a363bb6c82 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -54,16 +54,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region factory - static createNew(accessor: IEditorGroupsAccessor, label: string, instantiationService: IInstantiationService): IEditorGroupView { - return instantiationService.createInstance(EditorGroupView, accessor, null, label); + static createNew(accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView { + return instantiationService.createInstance(EditorGroupView, accessor, null, index); } - static createFromSerialized(serialized: ISerializedEditorGroup, accessor: IEditorGroupsAccessor, label: string, instantiationService: IInstantiationService): IEditorGroupView { - return instantiationService.createInstance(EditorGroupView, accessor, serialized, label); + static createFromSerialized(serialized: ISerializedEditorGroup, accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView { + return instantiationService.createInstance(EditorGroupView, accessor, serialized, index); } - static createCopy(copyFrom: IEditorGroupView, accessor: IEditorGroupsAccessor, label: string, instantiationService: IInstantiationService): IEditorGroupView { - return instantiationService.createInstance(EditorGroupView, accessor, copyFrom, label); + static createCopy(copyFrom: IEditorGroupView, accessor: IEditorGroupsAccessor, index: number, instantiationService: IInstantiationService): IEditorGroupView { + return instantiationService.createInstance(EditorGroupView, accessor, copyFrom, index); } //#endregion @@ -119,7 +119,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { constructor( private accessor: IEditorGroupsAccessor, from: IEditorGroupView | ISerializedEditorGroup, - private _label: string, + private _index: number, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @@ -656,8 +656,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._group; } + get index(): number { + return this._index; + } + get label(): string { - return this._label; + return localize('groupLabel', "Group {0}", this._index + 1); } get disposed(): boolean { @@ -668,10 +672,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._whenRestored; } - setLabel(label: string): void { - if (this._label !== label) { - this._label = label; - this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_LABEL }); + notifyIndexChanged(newIndex: number): void { + if (this._index !== newIndex) { + this._index = newIndex; + this._onDidGroupChange.fire({ kind: GroupChangeKind.GROUP_INDEX }); } } diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 0d0ac0c72310d90d9fd7c7a6fa6ffb23e11bd67b..81f9ab6a168cc826baf44ea375802df53a141efd 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -24,7 +24,6 @@ import { assign } from 'vs/base/common/objects'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; import { EditorDropTarget } from 'vs/workbench/browser/parts/editor/editorDropTarget'; -import { localize } from 'vs/nls'; import { Color } from 'vs/base/common/color'; import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -95,6 +94,9 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro private readonly _onDidActiveGroupChange: Emitter = this._register(new Emitter()); readonly onDidActiveGroupChange: Event = this._onDidActiveGroupChange.event; + private readonly _onDidGroupIndexChange: Emitter = this._register(new Emitter()); + readonly onDidGroupIndexChange: Event = this._onDidGroupIndexChange.event; + private readonly _onDidActivateGroup: Emitter = this._register(new Emitter()); readonly onDidActivateGroup: Event = this._onDidActivateGroup.event; @@ -424,8 +426,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro } }); - // Update labels - this.updateGroupLabels(); + // Notify group index change given layout has changed + this.notifyGroupIndexChange(); // Restore focus as needed if (restoreFocus) { @@ -484,25 +486,22 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Event this._onDidAddGroup.fire(newGroupView); - // Update labels - this.updateGroupLabels(); + // Notify group index change given a new group was added + this.notifyGroupIndexChange(); return newGroupView; } private doCreateGroupView(from?: IEditorGroupView | ISerializedEditorGroup | null): IEditorGroupView { - // Label: just use the number of existing groups as label - const label = this.getGroupLabel(this.count + 1); - // Create group view let groupView: IEditorGroupView; if (from instanceof EditorGroupView) { - groupView = EditorGroupView.createCopy(from, this, label, this.instantiationService); + groupView = EditorGroupView.createCopy(from, this, this.count, this.instantiationService); } else if (isSerializedEditorGroup(from)) { - groupView = EditorGroupView.createFromSerialized(from, this, label, this.instantiationService); + groupView = EditorGroupView.createFromSerialized(from, this, this.count, this.instantiationService); } else { - groupView = EditorGroupView.createNew(this, label, this.instantiationService); + groupView = EditorGroupView.createNew(this, this.count, this.instantiationService); } // Keep in map @@ -516,8 +515,13 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Track editor change groupDisposables.add(groupView.onDidGroupChange(e => { - if (e.kind === GroupChangeKind.EDITOR_ACTIVE) { - this.updateContainer(); + switch (e.kind) { + case GroupChangeKind.EDITOR_ACTIVE: + this.updateContainer(); + break; + case GroupChangeKind.GROUP_INDEX: + this._onDidGroupIndexChange.fire(groupView); + break; } })); @@ -643,8 +647,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this._activeGroup.focus(); } - // Update labels - this.updateGroupLabels(); + // Notify group index change given a group was removed + this.notifyGroupIndexChange(); // Update container this.updateContainer(); @@ -675,6 +679,9 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Event this._onDidMoveGroup.fire(sourceView); + // Notify group index change given a group was moved + this.notifyGroupIndexChange(); + return sourceView; } @@ -784,6 +791,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro centerLayout(active: boolean): void { this.centeredLayoutWidget.activate(active); + this._activeGroup.focus(); } @@ -909,19 +917,8 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro toggleClass(this.container, 'empty', this.isEmpty()); } - private updateGroupLabels(): void { - - // Since our labels are created using the index of the - // group, adding/removing a group might produce gaps. - // So we iterate over all groups and reassign the label - // based on the index. - this.getGroups(GroupsOrder.GRID_APPEARANCE).forEach((group, index) => { - group.setLabel(this.getGroupLabel(index + 1)); - }); - } - - private getGroupLabel(index: number): string { - return localize('groupLabel', "Group {0}", index); + private notifyGroupIndexChange(): void { + this.getGroups(GroupsOrder.GRID_APPEARANCE).forEach((group, index) => group.notifyIndexChanged(index)); } private isEmpty(): boolean { diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 7c36ea51726f7278ff9a18d5be6cf2213d3aa2f2..abb2f710aa564c5f65b597e949fd1ad444a619d6 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -237,7 +237,7 @@ import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform' 'workbench.useExperimentalGridLayout': { 'type': 'boolean', 'description': nls.localize('workbench.useExperimentalGridLayout', "Enables the grid layout for the workbench. This setting may enable additional layout options for workbench components."), - 'default': false, + 'default': true, 'scope': ConfigurationScope.APPLICATION } } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 9827b74e5191c4a0cc759f4d28247e437fa5bf72..97f5aa1e79d6db113bd88c57c1217410736a95e3 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -30,6 +30,8 @@ export const NoEditorsVisibleContext: ContextKeyExpr = EditorsVisibleContext.toN export const TextCompareEditorVisibleContext = new RawContextKey('textCompareEditorVisible', false); export const TextCompareEditorActiveContext = new RawContextKey('textCompareEditorActive', false); export const ActiveEditorGroupEmptyContext = new RawContextKey('activeEditorGroupEmpty', false); +export const ActiveEditorGroupIndexContext = new RawContextKey('activeEditorGroupIndex', -1); +export const ActiveEditorGroupLastContext = new RawContextKey('activeEditorGroupLast', false); export const MultipleEditorGroupsContext = new RawContextKey('multipleEditorGroups', false); export const SingleEditorGroupsContext = MultipleEditorGroupsContext.toNegated(); export const InEditorZenModeContext = new RawContextKey('inZenMode', false); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index bffa827671fc3f9592ab72be0fe703e3b9e71ef4..a069bee3bf99a66bf0240c81934cecdb4f0092dc 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -141,7 +141,7 @@ export class FilesRenderer implements ITreeRenderer { diff --git a/src/vs/workbench/services/editor/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts index a9ab498cc9828cd539a08d71a04a9577890e943c..6c9a974e6eaf86c1c3583c7646b34f6550c2ee96 100644 --- a/src/vs/workbench/services/editor/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -176,6 +176,11 @@ export interface IEditorGroupsService { */ readonly onDidLayout: Event; + /** + * An event for when the index of a group changes. + */ + readonly onDidGroupIndexChange: Event; + /** * The size of the editor groups area. */ @@ -343,7 +348,7 @@ export const enum GroupChangeKind { /* Group Changes */ GROUP_ACTIVE, - GROUP_LABEL, + GROUP_INDEX, /* Editor Changes */ EDITOR_OPEN, @@ -374,6 +379,14 @@ export interface IEditorGroup { */ readonly id: GroupIdentifier; + /** + * A number that indicates the position of this group in the visual + * order of groups from left to right and top to bottom. The lowest + * index will likely be top-left while the largest index in most + * cases should be bottom-right, but that depends on the grid. + */ + readonly index: number; + /** * A human readable label for the group. This label can change depending * on the layout of all editor groups. Clients should listen on the diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts index 477fdfc5bc791c4b4f62ed9e5be5670e6d8855af..042ca0152215688e8ce50f86096fa865dcb5846e 100644 --- a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -254,30 +254,60 @@ suite('EditorGroupsService', () => { part.dispose(); }); - test('groups labels', function () { + test('groups index / labels', function () { const part = createPart(); const rootGroup = part.groups[0]; const rightGroup = part.addGroup(rootGroup, GroupDirection.RIGHT); const downGroup = part.addGroup(rightGroup, GroupDirection.DOWN); - let labelChangeCounter = 0; + let groupIndexChangedCounter = 0; + const groupIndexChangedListener = part.onDidGroupIndexChange(() => { + groupIndexChangedCounter++; + }); + + let indexChangeCounter = 0; const labelChangeListener = downGroup.onDidGroupChange(e => { - if (e.kind === GroupChangeKind.GROUP_LABEL) { - labelChangeCounter++; + if (e.kind === GroupChangeKind.GROUP_INDEX) { + indexChangeCounter++; } }); + assert.equal(rootGroup.index, 0); + assert.equal(rightGroup.index, 1); + assert.equal(downGroup.index, 2); assert.equal(rootGroup.label, 'Group 1'); assert.equal(rightGroup.label, 'Group 2'); assert.equal(downGroup.label, 'Group 3'); part.removeGroup(rightGroup); + assert.equal(rootGroup.index, 0); + assert.equal(downGroup.index, 1); assert.equal(rootGroup.label, 'Group 1'); assert.equal(downGroup.label, 'Group 2'); - assert.equal(labelChangeCounter, 1); + assert.equal(indexChangeCounter, 1); + assert.equal(groupIndexChangedCounter, 1); + + part.moveGroup(downGroup, rootGroup, GroupDirection.UP); + assert.equal(downGroup.index, 0); + assert.equal(rootGroup.index, 1); + assert.equal(downGroup.label, 'Group 1'); + assert.equal(rootGroup.label, 'Group 2'); + assert.equal(indexChangeCounter, 2); + assert.equal(groupIndexChangedCounter, 3); + + const newFirstGroup = part.addGroup(downGroup, GroupDirection.UP); + assert.equal(newFirstGroup.index, 0); + assert.equal(downGroup.index, 1); + assert.equal(rootGroup.index, 2); + assert.equal(newFirstGroup.label, 'Group 1'); + assert.equal(downGroup.label, 'Group 2'); + assert.equal(rootGroup.label, 'Group 3'); + assert.equal(indexChangeCounter, 3); + assert.equal(groupIndexChangedCounter, 6); labelChangeListener.dispose(); + groupIndexChangedListener.dispose(); part.dispose(); }); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 48ebaf8e296d2273ba5eac90402f1db2a3925b0f..3bebf0fa5a93bfa548d305ece33d7f7aeb241cd9 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -665,6 +665,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { onDidAddGroup: Event = Event.None; onDidRemoveGroup: Event = Event.None; onDidMoveGroup: Event = Event.None; + onDidGroupIndexChange: Event = Event.None; onDidLayout: Event = Event.None; orientation: any; @@ -761,6 +762,7 @@ export class TestEditorGroup implements IEditorGroupView { disposed: boolean; editors: ReadonlyArray = []; label: string; + index: number; whenRestored: Promise = Promise.resolve(undefined); element: HTMLElement; minimumWidth: number; @@ -839,7 +841,7 @@ export class TestEditorGroup implements IEditorGroupView { isEmpty(): boolean { return true; } setActive(_isActive: boolean): void { } - setLabel(_label: string): void { } + notifyIndexChanged(_index: number): void { } dispose(): void { } toJSON(): object { return Object.create(null); } layout(_width: number, _height: number): void { }