From e6785ffe951003aed90bcc25bc8acadce1131c98 Mon Sep 17 00:00:00 2001 From: Anthony Dresser Date: Thu, 28 Mar 2019 13:06:16 -0700 Subject: [PATCH] Merge from vscode de81ccf04849309f843db21130c806a5783678f7 (#4738) --- extensions/git/src/commands.ts | 2 +- .../extension.webpack.config.js | 13 +- .../server/extension.webpack.config.js | 12 +- package.json | 2 +- .../parts/modelComponents/queryTextEditor.ts | 2 +- src/typings/spdlog.d.ts | 2 + src/vs/base/browser/ui/dialog/dialog.css | 9 +- src/vs/base/browser/ui/dialog/dialog.ts | 23 +- src/vs/base/browser/ui/list/list.ts | 1 + src/vs/base/browser/ui/list/listView.ts | 16 +- src/vs/base/browser/ui/list/listWidget.ts | 4 +- src/vs/base/browser/ui/tree/abstractTree.ts | 11 +- src/vs/base/browser/ui/tree/asyncDataTree.ts | 3 +- src/vs/base/node/extfs.ts | 8 +- src/vs/editor/browser/config/configuration.ts | 9 +- .../editor/browser/widget/codeEditorWidget.ts | 2 +- .../common/config/commonEditorConfig.ts | 25 +- src/vs/editor/common/config/editorOptions.ts | 19 +- src/vs/editor/common/config/fontInfo.ts | 4 +- src/vs/editor/common/services/modelService.ts | 2 +- .../common/services/modelServiceImpl.ts | 6 +- .../goToDefinition/goToDefinitionCommands.ts | 20 +- .../referenceSearch/referencesModel.ts | 15 +- .../test/common/mocks/testConfiguration.ts | 2 +- .../test/common/services/modelService.test.ts | 2 +- src/vs/monaco.d.ts | 6 +- src/vs/platform/files/common/files.ts | 6 +- src/vs/platform/list/browser/listService.ts | 1 - src/vs/platform/log/node/spdlogService.ts | 2 +- .../platform/menubar/electron-main/menubar.ts | 2 +- .../common/remoteAgentFileSystemChannel.ts | 5 +- src/vs/platform/request/node/request.ts | 2 +- src/vs/platform/theme/common/styler.ts | 2 + .../url/electron-browser/urlService.ts | 10 +- .../api/node/extHostExtensionService.ts | 5 +- .../api/node/extHostOutputService.ts | 74 ++- src/vs/workbench/api/node/extHostTask.ts | 1 + .../workbench/browser/actions/listCommands.ts | 14 +- .../browser/parts/titlebar/menubarControl.ts | 2 +- .../browser/callHierarchyPeek.ts | 2 + .../browser/callHierarchyTree.ts | 8 +- .../browser/media/callHierarchy.css | 1 + .../WordWrap_16x.svg | 0 .../codeEditor/browser/toggleWordWrap.ts | 4 +- .../comments/browser/commentThreadWidget.ts | 48 +- .../browser/commentsEditorContribution.ts | 35 +- .../comments/common/commentThreadWidget.ts | 3 +- .../contrib/debug/browser/callStackView.ts | 39 +- .../contrib/debug/browser/debugActionItems.ts | 4 + .../contrib/debug/browser/debugActions.ts | 502 +----------------- .../contrib/debug/browser/debugCommands.ts | 14 +- .../electron-browser/debug.contribution.ts | 2 +- .../debug/electron-browser/debugService.ts | 1 + .../electron-browser/extensionsViews.ts | 4 +- .../contrib/files/browser/fileActions.ts | 7 +- .../views/explorerDecorationsProvider.ts | 4 - .../files/browser/views/explorerView.ts | 31 +- .../files/browser/views/explorerViewer.ts | 11 +- .../contrib/files/common/explorerModel.ts | 6 + .../contrib/markers/browser/markers.ts | 4 +- .../preferences/browser/preferencesActions.ts | 1 - .../preferences/browser/settingsTree.ts | 15 +- .../preferences.contribution.ts | 40 +- .../electron-browser/task.contribution.ts | 2 + .../terminal/browser/terminalInstance.ts | 17 +- src/vs/workbench/electron-browser/main.ts | 23 +- .../dialogs/browser/fileDialogService.ts | 3 +- .../electron-browser/extensionService.ts | 12 +- .../services/extensions/node/proxyResolver.ts | 7 +- .../services/files2/common/fileService2.ts | 22 +- .../files2/node/diskFileSystemProvider.ts | 31 +- .../files2/test/node/diskFileService.test.ts | 19 +- .../output/common/outputChannelModel.ts | 2 +- .../services/output/node/outputAppender.ts | 2 +- .../output/node/outputChannelModelService.ts | 101 +++- .../api/mainThreadDocumentsAndEditors.test.ts | 14 +- yarn.lock | 8 +- 77 files changed, 561 insertions(+), 834 deletions(-) rename src/vs/workbench/contrib/codeEditor/browser/{suggestEnabledInput => }/WordWrap_16x.svg (100%) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 3ef0a946f82e..5ebf8928cdf3 100755 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -93,7 +93,7 @@ class CreateBranchItem implements QuickPickItem { constructor(private cc: CommandCenter) { } - get label(): string { return localize('create branch', '$(plus) Create new branch'); } + get label(): string { return localize('create branch', '$(plus) Create new branch...'); } get description(): string { return ''; } get alwaysShow(): boolean { return true; } diff --git a/extensions/json-language-features/extension.webpack.config.js b/extensions/json-language-features/extension.webpack.config.js index 57819b3149e2..7d507e3df0ae 100644 --- a/extensions/json-language-features/extension.webpack.config.js +++ b/extensions/json-language-features/extension.webpack.config.js @@ -11,7 +11,7 @@ const withDefaults = require('../shared.webpack.config'); const path = require('path'); var webpack = require('webpack'); -module.exports = withDefaults({ +const config = withDefaults({ context: path.join(__dirname, 'client'), entry: { extension: './src/jsonMain.ts', @@ -19,9 +19,10 @@ module.exports = withDefaults({ output: { filename: 'jsonMain.js', path: path.join(__dirname, 'client', 'dist') - }, - plugins: [ - new webpack.IgnorePlugin(/vertx/) // request-light dependendeny - ] - + } }); + +// add plugin, don't replace inherited +config.plugins.push(new webpack.IgnorePlugin(/vertx/)); // request-light dependency + +module.exports = config; \ No newline at end of file diff --git a/extensions/json-language-features/server/extension.webpack.config.js b/extensions/json-language-features/server/extension.webpack.config.js index 2a77c2ba2fc9..5c6783fb6e9a 100644 --- a/extensions/json-language-features/server/extension.webpack.config.js +++ b/extensions/json-language-features/server/extension.webpack.config.js @@ -11,7 +11,7 @@ const withDefaults = require('../../shared.webpack.config'); const path = require('path'); var webpack = require('webpack'); -module.exports = withDefaults({ +const config = withDefaults({ context: path.join(__dirname), entry: { extension: './src/jsonServerMain.ts', @@ -19,8 +19,10 @@ module.exports = withDefaults({ output: { filename: 'jsonServerMain.js', path: path.join(__dirname, 'dist') - }, - plugins: [ - new webpack.IgnorePlugin(/vertx/) // request-light dependendeny - ] + } }); + +// add plugin, don't replace inherited +config.plugins.push(new webpack.IgnorePlugin(/vertx/)); // request-light dependency + +module.exports = config; diff --git a/package.json b/package.json index 4727c59096f9..7a359c2fd522 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "sanitize-html": "^1.19.1", "semver": "^5.5.0", "slickgrid": "github:anthonydresser/SlickGrid#2.3.29", - "spdlog": "0.7.2", + "spdlog": "0.8.1", "sudo-prompt": "8.2.0", "v8-inspect-profiler": "^0.0.20", "vscode-chokidar": "1.6.5", diff --git a/src/sql/parts/modelComponents/queryTextEditor.ts b/src/sql/parts/modelComponents/queryTextEditor.ts index 7ff5a493c74b..39ddfc39d77a 100644 --- a/src/sql/parts/modelComponents/queryTextEditor.ts +++ b/src/sql/parts/modelComponents/queryTextEditor.ts @@ -133,7 +133,7 @@ export class QueryTextEditor extends BaseTextEditor { public setHeightToScrollHeight(configChanged?: boolean): void { let editorWidget = this.getControl() as ICodeEditor; if (!this._config) { - this._config = new Configuration(undefined, editorWidget.getDomNode(), this.accessibilityService); + this._config = new Configuration(true, undefined, editorWidget.getDomNode(), this.accessibilityService); this._scrollbarHeight = this._config.editor.viewInfo.scrollbar.horizontalScrollbarSize; } let editorWidgetModel = editorWidget.getModel(); diff --git a/src/typings/spdlog.d.ts b/src/typings/spdlog.d.ts index 3b84ffd6dc74..32b3c756ac75 100644 --- a/src/typings/spdlog.d.ts +++ b/src/typings/spdlog.d.ts @@ -7,6 +7,8 @@ declare module 'spdlog' { export const version: string; export function setAsyncMode(bufferSize: number, flushInterval: number): void; + export function createRotatingLogger(name: string, filename: string, filesize: number, filecount: number): RotatingLogger; + export function createRotatingLoggerAsync(name: string, filename: string, filesize: number, filecount: number): Promise; export enum LogLevel { CRITICAL, diff --git a/src/vs/base/browser/ui/dialog/dialog.css b/src/vs/base/browser/ui/dialog/dialog.css index 791be617fdf3..241012a8265b 100644 --- a/src/vs/base/browser/ui/dialog/dialog.css +++ b/src/vs/base/browser/ui/dialog/dialog.css @@ -10,17 +10,16 @@ left:0; top:0; z-index: 2000; + display: flex; + justify-content: center; + align-items: center; } /** Dialog: Container */ .monaco-workbench .dialog-box { - position: absolute; display: flex; flex-direction: column-reverse; - top: 200px; - left: 50%; - margin-left: -250px; - width: 500px; + min-width: 500px; min-height: 75px; padding: 5px; } diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 61311f4aeddf..0ee6b1f6555a 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -9,7 +9,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { $, hide, show, EventHelper, clearNode, removeClasses, addClass, removeNode } from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Color } from 'vs/base/common/color'; import { ButtonGroup, IButtonStyles } from 'vs/base/browser/ui/button/button'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -26,6 +26,7 @@ export interface IDialogStyles extends IButtonStyles { dialogForeground?: Color; dialogBackground?: Color; dialogShadow?: Color; + dialogBorder?: Color; } export class Dialog extends Disposable { @@ -49,13 +50,15 @@ export class Dialog extends Disposable { const messageRowElement = this.element.appendChild($('.dialog-message-row')); this.iconElement = messageRowElement.appendChild($('.dialog-icon')); const messageContainer = messageRowElement.appendChild($('.dialog-message-container')); - const messageElement = messageContainer.appendChild($('.dialog-message')); - messageElement.innerText = this.message; + if (this.options.detail) { - const messageDetailElement = messageContainer.appendChild($('.dialog-message-detail')); - messageDetailElement.innerText = this.options.detail; + const messageElement = messageContainer.appendChild($('.dialog-message')); + messageElement.innerText = this.message; } + const messageDetailElement = messageContainer.appendChild($('.dialog-message-detail')); + messageDetailElement.innerText = this.options.detail ? this.options.detail : message; + const toolbarRowElement = this.element.appendChild($('.dialog-toolbar-row')); this.toolbarContainer = toolbarRowElement.appendChild($('.dialog-toolbar')); } @@ -87,14 +90,14 @@ export class Dialog extends Disposable { })); }); - this._register(domEvent(this.element, 'keydown', true)((e: KeyboardEvent) => { + this._register(domEvent(window, 'keydown', true)((e: KeyboardEvent) => { const evt = new StandardKeyboardEvent(e); - if (evt.equals(KeyCode.Enter)) { + if (evt.equals(KeyCode.Enter) || evt.equals(KeyCode.Space)) { return; } if (this.buttonGroup) { - if ((evt.shiftKey && evt.equals(KeyCode.Tab)) || evt.equals(KeyCode.LeftArrow)) { + if (evt.equals(KeyMod.Shift | KeyCode.Tab) || evt.equals(KeyCode.LeftArrow)) { focusedButton = focusedButton + this.buttonGroup.buttons.length - 1; focusedButton = focusedButton % this.buttonGroup.buttons.length; this.buttonGroup.buttons[focusedButton].focus(); @@ -108,7 +111,7 @@ export class Dialog extends Disposable { EventHelper.stop(e, true); })); - this._register(domEvent(this.element, 'keyup', true)((e: KeyboardEvent) => { + this._register(domEvent(window, 'keyup', true)((e: KeyboardEvent) => { EventHelper.stop(e, true); const evt = new StandardKeyboardEvent(e); @@ -159,11 +162,13 @@ export class Dialog extends Disposable { const fgColor = style.dialogForeground ? `${style.dialogForeground}` : null; const bgColor = style.dialogBackground ? `${style.dialogBackground}` : null; const shadowColor = style.dialogShadow ? `0 0px 8px ${style.dialogShadow}` : null; + const border = style.dialogBorder ? `1px solid ${style.dialogBorder}` : null; if (this.element) { this.element.style.color = fgColor; this.element.style.backgroundColor = bgColor; this.element.style.boxShadow = shadowColor; + this.element.style.border = border; if (this.buttonGroup) { this.buttonGroup.buttons.forEach(button => button.style(style)); diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index 279343ea2a0f..f3996b8c896c 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -11,6 +11,7 @@ export interface IListVirtualDelegate { getHeight(element: T): number; getTemplateId(element: T): string; hasDynamicHeight?(element: T): boolean; + setDynamicHeight?(element: T, height: number): void; } export interface IListRenderer { diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 008ad0b6d197..ef62c551548d 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -191,15 +191,7 @@ export class ListView implements ISpliceable, IDisposable { readonly onDidChangeContentHeight: Event = Event.latch(this._onDidChangeContentHeight.event); get contentHeight(): number { return this.rangeMap.size; } - readonly onDidScroll: Event; - - // private _onDragStart = new Emitter<{ element: T, uri: string, event: DragEvent }>(); - // readonly onDragStart = this._onDragStart.event; - - // readonly onDragOver: Event>; - // readonly onDragLeave: Event; - // readonly onDrop: Event>; - // readonly onDragEnd: Event; + get onDidScroll(): Event { return this.scrollableElement.onScroll; } constructor( container: HTMLElement, @@ -253,7 +245,6 @@ export class ListView implements ISpliceable, IDisposable { this.disposables = [this.rangeMap, this.gesture, this.scrollableElement, this.cache]; - this.onDidScroll = Event.signal(this.scrollableElement.onScroll); this.scrollableElement.onScroll(this.onScroll, this, this.disposables); domEvent(this.rowsContainer, TouchEventType.Change)(this.onTouchChange, this, this.disposables); @@ -1105,6 +1096,11 @@ export class ListView implements ISpliceable, IDisposable { } item.size = row.domNode!.offsetHeight; + + if (this.virtualDelegate.setDynamicHeight) { + this.virtualDelegate.setDynamicHeight(item.element, item.size); + } + item.lastDynamicHeightWidth = this.renderWidth; this.rowsContainer.removeChild(row.domNode!); this.cache.release(row); diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index b8fcceb5c5fa..5de959993c52 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -20,7 +20,7 @@ import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, import { ListView, IListViewOptions, IListViewDragAndDrop, IAriaSetProvider } from './listView'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; -import { ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; import { ISpliceable } from 'vs/base/common/sequence'; import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice'; import { clamp } from 'vs/base/common/numbers'; @@ -1112,7 +1112,7 @@ export class List implements ISpliceable, IDisposable { return Event.map(this._onPin.event, indexes => this.toListEvent({ indexes })); } - get onDidScroll(): Event { return this.view.onDidScroll; } + get onDidScroll(): Event { return this.view.onDidScroll; } get onMouseClick(): Event> { return this.view.onMouseClick; } get onMouseDblClick(): Event> { return this.view.onMouseDblClick; } get onMouseMiddleClick(): Event> { return this.view.onMouseMiddleClick; } diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index ec7a37414533..ba898d1458e7 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -24,6 +24,7 @@ import { disposableTimeout } from 'vs/base/common/async'; import { isMacintosh } from 'vs/base/common/platform'; import { values } from 'vs/base/common/map'; import { clamp } from 'vs/base/common/numbers'; +import { ScrollEvent } from 'vs/base/common/scrollable'; function asTreeDragAndDropData(data: IDragAndDropData): IDragAndDropData { if (data instanceof ElementsDragAndDropData) { @@ -177,6 +178,12 @@ export class ComposedTreeDelegate implements IListV hasDynamicHeight(element: N): boolean { return !!this.delegate.hasDynamicHeight && this.delegate.hasDynamicHeight(element.element); } + + setDynamicHeight(element: N, height: number): void { + if (this.delegate.setDynamicHeight) { + this.delegate.setDynamicHeight(element.element, height); + } + } } interface ITreeListTemplateData { @@ -502,7 +509,7 @@ class TypeFilterController implements IDisposable { .map(e => new StandardKeyboardEvent(e)) .filter(this.keyboardNavigationEventFilter || (() => true)) .filter(() => this.automaticKeyboardNavigation || this.triggered) - .filter(e => isPrintableCharEvent(e) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? e.altKey : e.ctrlKey) && !e.shiftKey))) + .filter(e => isPrintableCharEvent(e) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? (e.altKey && !e.metaKey) : e.ctrlKey) && !e.shiftKey))) .forEach(e => { e.stopPropagation(); e.preventDefault(); }) .event; @@ -971,7 +978,7 @@ export abstract class AbstractTree implements IDisposable private focusNavigationFilter: ((node: ITreeNode) => boolean) | undefined; protected disposables: IDisposable[] = []; - get onDidScroll(): Event { return this.view.onDidScroll; } + get onDidScroll(): Event { return this.view.onDidScroll; } get onDidChangeFocus(): Event> { return this.eventBufferer.wrapEvent(this.focus.onDidChange); } get onDidChangeSelection(): Event> { return this.eventBufferer.wrapEvent(this.selection.onDidChange); } diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 6fe80936f3b7..fc6eb1fa0d41 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -17,6 +17,7 @@ import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors'; import { toggleClass } from 'vs/base/browser/dom'; import { values } from 'vs/base/common/map'; +import { ScrollEvent } from 'vs/base/common/scrollable'; interface IAsyncDataTreeNode { element: TInput | T; @@ -296,7 +297,7 @@ export class AsyncDataTree implements IDisposable protected readonly disposables: IDisposable[] = []; - get onDidScroll(): Event { return this.tree.onDidScroll; } + get onDidScroll(): Event { return this.tree.onDidScroll; } get onDidChangeFocus(): Event> { return Event.map(this.tree.onDidChangeFocus, asTreeEvent); } get onDidChangeSelection(): Event> { return Event.map(this.tree.onDidChangeSelection, asTreeEvent); } diff --git a/src/vs/base/node/extfs.ts b/src/vs/base/node/extfs.ts index f271a3e0dbaf..2ed2e1188e3d 100644 --- a/src/vs/base/node/extfs.ts +++ b/src/vs/base/node/extfs.ts @@ -205,10 +205,6 @@ export function del(path: string, tmpFolder: string, callback: (error: Error | n // do the heavy deletion outside the callers callback rmRecursive(pathInTemp, error => { - if (error) { - console.error(error); - } - if (done) { done(error); } @@ -308,12 +304,12 @@ export function mv(source: string, target: string, callback: (error: Error | nul return callback(err); } - fs.stat(target, (error, stat) => { + fs.lstat(target, (error, stat) => { if (error) { return callback(error); } - if (stat.isDirectory()) { + if (stat.isDirectory() || stat.isSymbolicLink()) { return callback(null); } diff --git a/src/vs/editor/browser/config/configuration.ts b/src/vs/editor/browser/config/configuration.ts index cd2725b67037..884dc7b87f28 100644 --- a/src/vs/editor/browser/config/configuration.ts +++ b/src/vs/editor/browser/config/configuration.ts @@ -320,8 +320,13 @@ export class Configuration extends CommonEditorConfiguration { private readonly _elementSizeObserver: ElementSizeObserver; - constructor(options: IEditorOptions, referenceDomElement: HTMLElement | null = null, private readonly accessibilityService: IAccessibilityService) { - super(options); + constructor( + isSimpleWidget: boolean, + options: IEditorOptions, + referenceDomElement: HTMLElement | null = null, + private readonly accessibilityService: IAccessibilityService + ) { + super(isSimpleWidget, options); this._elementSizeObserver = this._register(new ElementSizeObserver(referenceDomElement, () => this._onReferenceDomElementSizeChanged())); diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index e354933ac451..984ca4f4db1e 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -324,7 +324,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } protected _createConfiguration(options: editorOptions.IEditorOptions, accessibilityService: IAccessibilityService): editorCommon.IConfiguration { - return new Configuration(options, this._domElement, accessibilityService); + return new Configuration(this.isSimpleWidget, options, this._domElement, accessibilityService); } public getId(): string { diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 891c32de14c8..664f83d28134 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -65,6 +65,7 @@ const hasOwnProperty = Object.hasOwnProperty; export abstract class CommonEditorConfiguration extends Disposable implements editorCommon.IConfiguration { + public readonly isSimpleWidget: boolean; protected _rawOptions: editorOptions.IEditorOptions; protected _validatedOptions: editorOptions.IValidatedEditorOptions; public editor: editorOptions.InternalEditorOptions; @@ -74,9 +75,11 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed private _onDidChange = this._register(new Emitter()); public readonly onDidChange: Event = this._onDidChange.event; - constructor(options: editorOptions.IEditorOptions) { + constructor(isSimpleWidget: boolean, options: editorOptions.IEditorOptions) { super(); + this.isSimpleWidget = isSimpleWidget; + // Do a "deep clone of sorts" on the incoming options this._rawOptions = objects.mixin({}, options || {}); this._rawOptions.scrollbar = objects.mixin({}, this._rawOptions.scrollbar || {}); @@ -122,7 +125,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed private _computeInternalOptions(): editorOptions.InternalEditorOptions { const opts = this._validatedOptions; const partialEnv = this._getEnvConfiguration(); - const bareFontInfo = BareFontInfo.createFromRawSettings(this._rawOptions, partialEnv.zoomLevel); + const bareFontInfo = BareFontInfo.createFromRawSettings(this._rawOptions, partialEnv.zoomLevel, this.isSimpleWidget); const env: editorOptions.IEnvironmentalOptions = { outerWidth: partialEnv.outerWidth, outerHeight: partialEnv.outerHeight, @@ -688,8 +691,8 @@ const editorConfiguration: IConfigurationNode = { type: 'number', default: EDITOR_DEFAULTS.contribInfo.suggest.maxVisibleSuggestions, minimum: 1, - maximum: 12, - description: nls.localize('suggest.maxVisibleSuggestions', "Controls how many suggestions IntelliSense will show before showing a scrollbar.") + maximum: 15, + description: nls.localize('suggest.maxVisibleSuggestions', "Controls how many suggestions IntelliSense will show before showing a scrollbar (maximum 15).") }, 'editor.suggest.filteredTypes': { type: 'object', @@ -828,15 +831,15 @@ const editorConfiguration: IConfigurationNode = { }, } }, - 'editor.gotoLocation.many': { - description: nls.localize('editor.gotoLocation.many', "Controls the behaviour of 'go to'-commands, like go to definition, when multiple target locations exist."), + 'editor.gotoLocation.multiple': { + description: nls.localize('editor.gotoLocation.multiple', "Controls the behavior of 'Go To' commands, like Go To Definition, when multiple target locations exist."), type: 'string', - enum: ['peek', 'revealAndPeek', 'reveal'], - default: 'peek', + enum: ['peek', 'gotoAndPeek', 'goto'], + default: EDITOR_DEFAULTS.contribInfo.gotoLocation.multiple, enumDescriptions: [ - nls.localize('editor.gotoLocation.many.peek', 'Show peek view of the results at the request location'), - nls.localize('editor.gotoLocation.many.revealAndPeek', 'Reveal the first result and show peek view at its location'), - nls.localize('editor.gotoLocation.many.reveal', 'Reveal the first result and ignore others') + nls.localize('editor.gotoLocation.multiple.peek', 'Show peek view of the results (default)'), + nls.localize('editor.gotoLocation.multiple.gotoAndPeek', 'Go to the primary result and show a peek view'), + nls.localize('editor.gotoLocation.multiple.goto', 'Go to the primary result and ignore others') ] }, 'editor.selectionHighlight': { diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 779156151b76..61ec6d7e9a58 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -222,7 +222,7 @@ export interface IGotoLocationOptions { /** * Control how goto-command work when having multiple results. */ - many?: 'peek' | 'revealAndPeek' | 'reveal'; + multiple?: 'peek' | 'gotoAndPeek' | 'goto'; } /** @@ -270,7 +270,7 @@ export interface IEditorOptions { lineNumbers?: 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); /** * Render last line number when the file ends with a newline. - * Defaults to true on Windows/Mac and to false on Linux. + * Defaults to true. */ renderFinalNewline?: boolean; /** @@ -936,7 +936,7 @@ export interface InternalEditorHoverOptions { } export interface InternalGoToLocationOptions { - readonly many: 'peek' | 'revealAndPeek' | 'reveal'; + readonly multiple: 'peek' | 'gotoAndPeek' | 'goto'; } export interface InternalSuggestOptions { @@ -1401,7 +1401,8 @@ export class InternalEditorOptions { && a.localityBonus === b.localityBonus && a.shareSuggestSelections === b.shareSuggestSelections && a.showIcons === b.showIcons - && a.maxVisibleSuggestions === b.maxVisibleSuggestions; + && a.maxVisibleSuggestions === b.maxVisibleSuggestions + && objects.equals(a.filteredTypes, b.filteredTypes); } } @@ -1411,7 +1412,7 @@ export class InternalEditorOptions { } else if (!a || !b) { return false; } else { - return a.many === b.many; + return a.multiple === b.multiple; } } @@ -1946,7 +1947,7 @@ export class EditorOptionsValidator { localityBonus: _boolean(suggestOpts.localityBonus, defaults.localityBonus), shareSuggestSelections: _boolean(suggestOpts.shareSuggestSelections, defaults.shareSuggestSelections), showIcons: _boolean(suggestOpts.showIcons, defaults.showIcons), - maxVisibleSuggestions: _clampedInt(suggestOpts.maxVisibleSuggestions, defaults.maxVisibleSuggestions, 1, 12), + maxVisibleSuggestions: _clampedInt(suggestOpts.maxVisibleSuggestions, defaults.maxVisibleSuggestions, 1, 15), filteredTypes: isObject(suggestOpts.filteredTypes) ? suggestOpts.filteredTypes : Object.create(null) }; } @@ -1954,7 +1955,7 @@ export class EditorOptionsValidator { private static _santizeGotoLocationOpts(opts: IEditorOptions, defaults: InternalGoToLocationOptions): InternalGoToLocationOptions { const gotoOpts = opts.gotoLocation || {}; return { - many: _stringSet<'peek' | 'revealAndPeek' | 'reveal'>(gotoOpts.many, defaults.many, ['peek', 'revealAndPeek', 'reveal']) + multiple: _stringSet<'peek' | 'gotoAndPeek' | 'goto'>(gotoOpts.multiple, defaults.multiple, ['peek', 'gotoAndPeek', 'goto']) }; } @@ -2635,7 +2636,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { ariaLabel: nls.localize('editorViewAccessibleLabel', "Editor content"), renderLineNumbers: RenderLineNumbersType.On, renderCustomLineNumbers: null, - renderFinalNewline: (platform.isLinux ? false : true), + renderFinalNewline: true, selectOnLineNumbers: true, glyphMargin: true, revealHorizontalRightPadding: 30, @@ -2720,7 +2721,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { filteredTypes: Object.create(null) }, gotoLocation: { - many: 'peek' + multiple: 'peek' }, selectionHighlight: true, occurrencesHighlight: true, diff --git a/src/vs/editor/common/config/fontInfo.ts b/src/vs/editor/common/config/fontInfo.ts index b878c96bb805..fbdf61b98e73 100644 --- a/src/vs/editor/common/config/fontInfo.ts +++ b/src/vs/editor/common/config/fontInfo.ts @@ -80,7 +80,7 @@ export class BareFontInfo { fontSize?: number | string; lineHeight?: number | string; letterSpacing?: number | string; - }, zoomLevel: number): BareFontInfo { + }, zoomLevel: number, ignoreEditorZoom: boolean = false): BareFontInfo { let fontFamily = _string(opts.fontFamily, EDITOR_FONT_DEFAULTS.fontFamily); let fontWeight = _string(opts.fontWeight, EDITOR_FONT_DEFAULTS.fontWeight); @@ -105,7 +105,7 @@ export class BareFontInfo { let letterSpacing = safeParseFloat(opts.letterSpacing, 0); letterSpacing = clamp(letterSpacing, MINIMUM_LETTER_SPACING, MAXIMUM_LETTER_SPACING); - let editorZoomLevelMultiplier = 1 + (EditorZoom.getZoomLevel() * 0.1); + let editorZoomLevelMultiplier = 1 + (ignoreEditorZoom ? 0 : EditorZoom.getZoomLevel() * 0.1); fontSize *= editorZoomLevelMultiplier; lineHeight *= editorZoomLevelMultiplier; diff --git a/src/vs/editor/common/services/modelService.ts b/src/vs/editor/common/services/modelService.ts index 7222e3d78ddb..6b596e66368f 100644 --- a/src/vs/editor/common/services/modelService.ts +++ b/src/vs/editor/common/services/modelService.ts @@ -14,7 +14,7 @@ export const IModelService = createDecorator('modelService'); export interface IModelService { _serviceBrand: any; - createModel(value: string | ITextBufferFactory, languageSelection: ILanguageSelection | null, resource: URI | undefined, isForSimpleWidget?: boolean): ITextModel; + createModel(value: string | ITextBufferFactory, languageSelection: ILanguageSelection | null, resource?: URI, isForSimpleWidget?: boolean): ITextModel; updateModel(model: ITextModel, value: string | ITextBufferFactory): void; diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 42a1e59c016f..fc8e129553ee 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -190,7 +190,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { }; } - public getCreationOptions(language: string, resource: URI | null | undefined, isForSimpleWidget: boolean): ITextModelCreationOptions { + public getCreationOptions(language: string, resource: URI | undefined, isForSimpleWidget: boolean): ITextModelCreationOptions { let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource]; if (!creationOptions) { const editor = this._configurationService.getValue('editor', { overrideIdentifier: language, resource }); @@ -252,7 +252,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { // --- begin IModelService - private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI | null | undefined, isForSimpleWidget: boolean): ModelData { + private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI | undefined, isForSimpleWidget: boolean): ModelData { // create & save the model const options = this.getCreationOptions(languageIdentifier.language, resource, isForSimpleWidget); const model: TextModel = new TextModel(value, options, languageIdentifier, resource); @@ -343,7 +343,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { return [EditOperation.replaceMove(oldRange, textBuffer.getValueInRange(newRange, EndOfLinePreference.TextDefined))]; } - public createModel(value: string | ITextBufferFactory, languageSelection: ILanguageSelection | null, resource: URI | null | undefined, isForSimpleWidget: boolean = false): ITextModel { + public createModel(value: string | ITextBufferFactory, languageSelection: ILanguageSelection | null, resource?: URI, isForSimpleWidget: boolean = false): ITextModel { let modelData: ModelData; if (languageSelection) { diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts index 5832495666b7..a0480c7932eb 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts @@ -131,17 +131,19 @@ export class DefinitionAction extends EditorAction { alert(msg); const { gotoLocation } = editor.getConfiguration().contribInfo; - if (this._configuration.openInPeek || (gotoLocation.many === 'peek' && model.references.length > 1)) { + if (this._configuration.openInPeek || (gotoLocation.multiple === 'peek' && model.references.length > 1)) { this._openInPeek(editorService, editor, model); + } else if (editor.hasModel()) { - const next = model.nearestReference(editor.getModel().uri, editor.getPosition()); - if (next) { - const targetEditor = await this._openReference(editor, editorService, next, this._configuration.openToSide); - if (targetEditor && model.references.length > 1 && gotoLocation.many === 'revealAndPeek') { - this._openInPeek(editorService, targetEditor, model); - } else { - model.dispose(); - } + const next = model.firstReference(); + if (!next) { + return; + } + const targetEditor = await this._openReference(editor, editorService, next, this._configuration.openToSide); + if (targetEditor && model.references.length > 1 && gotoLocation.multiple === 'gotoAndPeek') { + this._openInPeek(editorService, targetEditor, model); + } else { + model.dispose(); } } } diff --git a/src/vs/editor/contrib/referenceSearch/referencesModel.ts b/src/vs/editor/contrib/referenceSearch/referencesModel.ts index 162b1ef51c43..f53a84c88ab7 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesModel.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesModel.ts @@ -23,7 +23,8 @@ export class OneReference { constructor( readonly parent: FileReferences, - private _range: IRange + private _range: IRange, + readonly isProviderFirst: boolean ) { this.id = defaultGenerator.nextId(); } @@ -173,6 +174,7 @@ export class ReferencesModel implements IDisposable { constructor(references: LocationLink[]) { this._disposables = []; // grouping and sorting + const [providersFirst] = references; references.sort(ReferencesModel._compareReferences); let current: FileReferences | undefined; @@ -187,7 +189,7 @@ export class ReferencesModel implements IDisposable { if (current.children.length === 0 || !Range.equalsRange(ref.range, current.children[current.children.length - 1].range)) { - let oneRef = new OneReference(current, ref.targetSelectionRange || ref.range); + let oneRef = new OneReference(current, ref.targetSelectionRange || ref.range, providersFirst === ref); this._disposables.push(oneRef.onRefChanged((e) => this._onDidChangeReferenceRange.fire(e))); this.references.push(oneRef); current.children.push(oneRef); @@ -267,6 +269,15 @@ export class ReferencesModel implements IDisposable { return undefined; } + firstReference(): OneReference | undefined { + for (const ref of this.references) { + if (ref.isProviderFirst) { + return ref; + } + } + return this.references[0]; + } + dispose(): void { dispose(this.groups); dispose(this._disposables); diff --git a/src/vs/editor/test/common/mocks/testConfiguration.ts b/src/vs/editor/test/common/mocks/testConfiguration.ts index 19d93ad21197..a32c6062b00f 100644 --- a/src/vs/editor/test/common/mocks/testConfiguration.ts +++ b/src/vs/editor/test/common/mocks/testConfiguration.ts @@ -11,7 +11,7 @@ import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibi export class TestConfiguration extends CommonEditorConfiguration { constructor(opts: IEditorOptions) { - super(opts); + super(false, opts); this._recomputeOptions(); } diff --git a/src/vs/editor/test/common/services/modelService.test.ts b/src/vs/editor/test/common/services/modelService.test.ts index b2248e330b98..d1c5712dd438 100644 --- a/src/vs/editor/test/common/services/modelService.test.ts +++ b/src/vs/editor/test/common/services/modelService.test.ts @@ -35,7 +35,7 @@ suite('ModelService', () => { }); test('EOL setting respected depending on root', () => { - const model1 = modelService.createModel('farboo', null, null); + const model1 = modelService.createModel('farboo', null); const model2 = modelService.createModel('farboo', null, URI.file(platform.isWindows ? 'c:\\myroot\\myfile.txt' : '/myroot/myfile.txt')); const model3 = modelService.createModel('farboo', null, URI.file(platform.isWindows ? 'c:\\other\\myfile.txt' : '/other/myfile.txt')); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index cc9ba4e61ece..a7e941775bfc 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2565,7 +2565,7 @@ declare namespace monaco.editor { /** * Control how goto-command work when having multiple results. */ - many?: 'peek' | 'revealAndPeek' | 'reveal'; + multiple?: 'peek' | 'gotoAndPeek' | 'goto'; } /** @@ -2608,7 +2608,7 @@ declare namespace monaco.editor { lineNumbers?: 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); /** * Render last line number when the file ends with a newline. - * Defaults to true on Windows/Mac and to false on Linux. + * Defaults to true. */ renderFinalNewline?: boolean; /** @@ -3208,7 +3208,7 @@ declare namespace monaco.editor { } export interface InternalGoToLocationOptions { - readonly many: 'peek' | 'revealAndPeek' | 'reveal'; + readonly multiple: 'peek' | 'gotoAndPeek' | 'goto'; } export interface InternalSuggestOptions { diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 8c281da83ecc..b8a691bdf670 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -205,9 +205,9 @@ export enum FileType { export interface IStat { type: FileType; - mtime?: number; - ctime?: number; - size?: number; + mtime: number; + ctime: number; + size: number; } export interface IWatchOptions { diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 87e3dafba908..f4afb80840a7 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -693,7 +693,6 @@ export class TreeResourceNavigator2 extends Disposable { } this._register(this.tree.onDidChangeSelection(e => this.onSelection(e))); - this._register(this.tree.onMouseDblClick(e => this.onSelection(e))); this._register(this.tree.onDidOpen(e => this.onSelection(e))); } diff --git a/src/vs/platform/log/node/spdlogService.ts b/src/vs/platform/log/node/spdlogService.ts index 7b5bfd2acfc9..ec885eaa77ac 100644 --- a/src/vs/platform/log/node/spdlogService.ts +++ b/src/vs/platform/log/node/spdlogService.ts @@ -25,7 +25,7 @@ export function createSpdLogService(processName: string, logLevel: LogLevel, log export function createRotatingLogger(name: string, filename: string, filesize: number, filecount: number): spdlog.RotatingLogger { const _spdlog: typeof spdlog = require.__$__nodeRequire('spdlog'); - return new _spdlog.RotatingLogger(name, filename, filesize, filecount); + return _spdlog.createRotatingLogger(name, filename, filesize, filecount); } class SpdLogService extends AbstractLogService implements ILogService { diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 8fa030b33003..5fca502778a4 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -580,7 +580,7 @@ export class Menubar { case StateType.Ready: return [new MenuItem({ - label: this.mnemonicLabel(nls.localize('miRestartToUpdate', "Restart to &&Update...")), click: () => { + label: this.mnemonicLabel(nls.localize('miRestartToUpdate', "Restart to &&Update")), click: () => { this.reportMenuActionTelemetry('RestartToUpdate'); this.updateService.quitAndInstall(); } diff --git a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts index a917f5fb27f0..6f7d1d124f6f 100644 --- a/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts +++ b/src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts @@ -10,6 +10,8 @@ import { generateUuid } from 'vs/base/common/uuid'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IFileChange, IFileSystemProvider, IStat, IWatchOptions } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; +import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; +import { OperatingSystem } from 'vs/base/common/platform'; export const REMOTE_FILE_SYSTEM_CHANNEL_NAME = 'remotefilesystem'; @@ -30,12 +32,13 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF private readonly _onDidChangeCapabilities = this._register(new Emitter()); readonly onDidChangeCapabilities: Event = this._onDidChangeCapabilities.event; - constructor(channel: IChannel) { + constructor(channel: IChannel, environment: Promise) { super(); this._session = generateUuid(); this._channel = channel; this.setCaseSensitive(true); + environment.then(remoteAgentEnvironment => this.setCaseSensitive(!!(remoteAgentEnvironment && remoteAgentEnvironment.os === OperatingSystem.Linux))); this._channel.listen('filechange', [this._session])((events) => { this._onDidChange.fire(events.map(RemoteExtensionsFileSystemProvider._createFileChange)); diff --git a/src/vs/platform/request/node/request.ts b/src/vs/platform/request/node/request.ts index 07c19a3449dd..6dd3eed71830 100644 --- a/src/vs/platform/request/node/request.ts +++ b/src/vs/platform/request/node/request.ts @@ -62,7 +62,7 @@ Registry.as(Extensions.Configuration) 'http.systemCertificates': { type: 'boolean', default: true, - description: localize('systemCertificates', "Controls whether CA certificates should be loaded from the OS.") + description: localize('systemCertificates', "Controls whether CA certificates should be loaded from the OS. (On Windows and Mac a reload of the window is required after turning this off.)") } } }); diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index 4df22261fc5c..479077ef303a 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -334,12 +334,14 @@ export interface IDialogStyleOverrides extends IButtonStyleOverrides { dialogForeground?: ColorIdentifier; dialogBackground?: ColorIdentifier; dialogShadow?: ColorIdentifier; + dialogBorder?: ColorIdentifier; } export const defaultDialogStyles = { dialogBackground: editorWidgetBackground, dialogForeground: foreground, dialogShadow: widgetShadow, + dialogBorder: contrastBorder, buttonForeground: buttonForeground, buttonBackground: buttonBackground, buttonHoverBackground: buttonHoverBackground, diff --git a/src/vs/platform/url/electron-browser/urlService.ts b/src/vs/platform/url/electron-browser/urlService.ts index b3d91dd13923..0f87fd18ad3d 100644 --- a/src/vs/platform/url/electron-browser/urlService.ts +++ b/src/vs/platform/url/electron-browser/urlService.ts @@ -9,8 +9,10 @@ import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProces import { URLServiceChannelClient, URLHandlerChannel } from 'vs/platform/url/node/urlIpc'; import { URLService } from 'vs/platform/url/common/urlService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import product from 'vs/platform/product/node/product'; export class RelayURLService extends URLService implements IURLHandler { + private urlService: IURLService; constructor( @@ -25,8 +27,12 @@ export class RelayURLService extends URLService implements IURLHandler { openerService.registerOpener(this); } - open(uri: URI): Promise { - return this.urlService.open(uri); + async open(uri: URI): Promise { + if (uri.scheme !== product.urlProtocol) { + return false; + } + + return await this.urlService.open(uri); } handleURL(uri: URI): Promise { diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 590de29b6b07..1f1c906ed5b4 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -643,11 +643,12 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { // to give the PH process a chance to flush any outstanding console // messages to the main process, we delay the exit() by some time setTimeout(() => { - if (!!this._initData.environment.extensionTestsLocationURI) { - // If extension tests are running, give the exit code to the renderer + // If extension tests are running, give the exit code to the renderer + if (this._initData.remoteAuthority && !!this._initData.environment.extensionTestsLocationURI) { this._mainThreadExtensionsProxy.$onExtensionHostExit(code); return; } + this._nativeExit(code); }, 500); } diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index 009eecea52bc..3d62d62d1b69 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -11,8 +11,9 @@ import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender import { toLocalISOString } from 'vs/base/common/date'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { dirExists, mkdirp } from 'vs/base/node/pfs'; -export abstract class AbstractExtHostOutputChannel extends Disposable implements vscode.OutputChannel { +abstract class AbstractExtHostOutputChannel extends Disposable implements vscode.OutputChannel { readonly _id: Promise; private readonly _name: string; @@ -83,7 +84,7 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements } } -export class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel { +class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel { constructor(name: string, proxy: MainThreadOutputServiceShape) { super(name, false, undefined, proxy); @@ -96,17 +97,13 @@ export class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel { } } -export class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel { +class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChannel { - private static _namePool = 1; private _appender: OutputAppender; - constructor(name: string, outputDir: string, proxy: MainThreadOutputServiceShape) { - const fileName = `${ExtHostOutputChannelBackedByFile._namePool++}-${name}`; - const file = URI.file(join(outputDir, `${fileName}.log`)); - - super(name, false, file, proxy); - this._appender = new OutputAppender(fileName, file.fsPath); + constructor(name: string, appender: OutputAppender, proxy: MainThreadOutputServiceShape) { + super(name, false, URI.file(appender.file), proxy); + this._appender = appender; } append(value: string): void { @@ -131,7 +128,7 @@ export class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChann } } -export class ExtHostLogFileOutputChannel extends AbstractExtHostOutputChannel { +class ExtHostLogFileOutputChannel extends AbstractExtHostOutputChannel { constructor(name: string, file: URI, proxy: MainThreadOutputServiceShape) { super(name, true, file, proxy); @@ -142,15 +139,31 @@ export class ExtHostLogFileOutputChannel extends AbstractExtHostOutputChannel { } } +let namePool = 1; +async function createExtHostOutputChannel(name: string, outputDirPromise: Promise, proxy: MainThreadOutputServiceShape): Promise { + try { + const outputDir = await outputDirPromise; + const fileName = `${namePool++}-${name}`; + const file = URI.file(join(outputDir, `${fileName}.log`)); + const appender = new OutputAppender(fileName, file.fsPath); + return new ExtHostOutputChannelBackedByFile(name, appender, proxy); + } catch (error) { + // Do not crash if logger cannot be created + console.log(error); + return new ExtHostPushOutputChannel(name, proxy); + } +} + export class ExtHostOutputService implements ExtHostOutputServiceShape { + private readonly _outputDir: Promise; private _proxy: MainThreadOutputServiceShape; - private _outputDir: string; private _channels: Map = new Map(); private _visibleChannelDisposable: IDisposable; constructor(logsLocation: URI, mainContext: IMainContext) { - this._outputDir = join(logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); + const outputDirPath = join(logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); + this._outputDir = dirExists(outputDirPath).then(exists => exists ? exists : mkdirp(outputDirPath)).then(() => outputDirPath); this._proxy = mainContext.getProxy(MainContext.MainThreadOutputService); } @@ -167,23 +180,32 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { } createOutputChannel(name: string): vscode.OutputChannel { - const channel = this._createOutputChannel(name); - channel._id.then(id => this._channels.set(id, channel)); - return channel; - } - - private _createOutputChannel(name: string): AbstractExtHostOutputChannel { name = name.trim(); if (!name) { throw new Error('illegal argument `name`. must not be falsy'); } else { - // Do not crash if logger cannot be created - try { - return new ExtHostOutputChannelBackedByFile(name, this._outputDir, this._proxy); - } catch (error) { - console.log(error); - return new ExtHostPushOutputChannel(name, this._proxy); - } + const extHostOutputChannel = createExtHostOutputChannel(name, this._outputDir, this._proxy); + extHostOutputChannel.then(channel => channel._id.then(id => this._channels.set(id, channel))); + return { + append(value: string): void { + extHostOutputChannel.then(channel => channel.append(value)); + }, + appendLine(value: string): void { + extHostOutputChannel.then(channel => channel.appendLine(value)); + }, + clear(): void { + extHostOutputChannel.then(channel => channel.clear()); + }, + show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { + extHostOutputChannel.then(channel => channel.show(columnOrPreserveFocus, preserveFocus)); + }, + hide(): void { + extHostOutputChannel.then(channel => channel.hide()); + }, + dispose(): void { + extHostOutputChannel.then(channel => channel.dispose()); + } + }; } } diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 2143b3bc2f00..08a720268806 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -367,6 +367,7 @@ class CustomExecutionData implements IDisposable { } public dispose(): void { + this._cancellationSource = undefined; dispose(this._disposables); } diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index 6534bfb266be..3dd82062744c 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -28,7 +28,7 @@ function ensureDOMFocus(widget: ListWidget | undefined): void { } } -function focusDown(accessor: ServicesAccessor, arg2?: number, loop: boolean = true): void { +function focusDown(accessor: ServicesAccessor, arg2?: number, loop: boolean = false): void { const focused = accessor.get(IListService).lastFocusedList; const count = typeof arg2 === 'number' ? arg2 : 1; @@ -165,7 +165,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -function focusUp(accessor: ServicesAccessor, arg2?: number, loop: boolean = true): void { +function focusUp(accessor: ServicesAccessor, arg2?: number, loop: boolean = false): void { const focused = accessor.get(IListService).lastFocusedList; const count = typeof arg2 === 'number' ? arg2 : 1; @@ -582,8 +582,14 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) { const list = focused; const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', false); - list.setSelection(list.getFocus(), fakeKeyboardEvent); - list.open(list.getFocus(), fakeKeyboardEvent); + const focus = list.getFocus(); + + if (focus.length > 0) { + list.toggleCollapsed(focus[0]); + } + + list.setSelection(focus, fakeKeyboardEvent); + list.open(focus, fakeKeyboardEvent); } // Tree diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 40a4cd23cdfb..8dfb1816d012 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -463,7 +463,7 @@ export class MenubarControl extends Disposable { return new Action('update.updating', nls.localize('installingUpdate', "Installing Update..."), undefined, false); case StateType.Ready: - return new Action('update.restart', nls.localize({ key: 'restartToUpdate', comment: ['&& denotes a mnemonic'] }, "Restart to &&Update..."), undefined, true, () => + return new Action('update.restart', nls.localize({ key: 'restartToUpdate', comment: ['&& denotes a mnemonic'] }, "Restart to &&Update"), undefined, true, () => this.updateService.quitAndInstall()); } } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index 5ed540796b94..88749118a423 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -157,6 +157,7 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { addClass(message, 'message'); parent.appendChild(message); this._message = message; + this._message.tabIndex = 0; const container = document.createElement('div'); addClass(container, 'results'); @@ -340,6 +341,7 @@ export class CallHierarchyTreePeekWidget extends PeekViewWidget { this.setMetaTitle(''); this._message.innerText = message; this._show(); + this._message.focus(); } async showItem(item: CallHierarchyItem): Promise { diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index dadd1f233d2c..57b19db87bdc 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -10,7 +10,6 @@ import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { symbolKindToCssClass, Location } from 'vs/editor/common/modes'; -import { ILabelService } from 'vs/platform/label/common/label'; import { Range } from 'vs/editor/common/core/range'; import { hash } from 'vs/base/common/hash'; @@ -67,21 +66,16 @@ export class CallRenderer implements ITreeRenderer, _index: number, template: CallRenderingTemplate): void { const { element, filterData } = node; - const detail = element.item.detail || this._labelService.getUriLabel(element.item.uri, { relative: true }); template.iconLabel.setLabel( element.item.name, - detail, + element.item.detail, { labelEscapeNewLines: true, matches: createMatches(filterData), diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css b/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css index ad70f92f9fe0..e256e7458dfa 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css +++ b/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css @@ -16,6 +16,7 @@ display: inherit; text-align: center; padding-top: 3em; + height: 100%; } .monaco-workbench .action-label.calls-to { diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/WordWrap_16x.svg b/src/vs/workbench/contrib/codeEditor/browser/WordWrap_16x.svg similarity index 100% rename from src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/WordWrap_16x.svg rename to src/vs/workbench/contrib/codeEditor/browser/WordWrap_16x.svg diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index cf543ca44456..29d025830771 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -279,7 +279,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_WORD_WRAP_ID, title: nls.localize('unwrapMinified', "Disable wrapping for this file"), - iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/contrib/codeEditor/electron-browser/media/WordWrap_16x.svg')) } + iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/contrib/codeEditor/browser/WordWrap_16x.svg')) } }, group: 'navigation', order: 1, @@ -293,7 +293,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_WORD_WRAP_ID, title: nls.localize('wrapMinified', "Enable wrapping for this file"), - iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/contrib/codeEditor/electron-browser/media/WordWrap_16x.svg')) } + iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/contrib/codeEditor/browser/WordWrap_16x.svg')) } }, group: 'navigation', order: 1, diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 348798e1fd55..6a60a2e530a7 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -199,25 +199,27 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._actionbarWidget = new ActionBar(actionsContainer, {}); this._disposables.push(this._actionbarWidget); - this._collapseAction = new Action('review.expand', nls.localize('label.collapse', "Collapse"), COLLAPSE_ACTION_CLASS, true, () => { - if (this._commentThread.comments.length === 0) { - if ((this._commentThread as modes.CommentThread2).commentThreadHandle === undefined) { - this.dispose(); - return Promise.resolve(); - } else { - const deleteCommand = (this._commentThread as modes.CommentThread2).deleteCommand; - if (deleteCommand) { - return this.commandService.executeCommand(deleteCommand.id, ...(deleteCommand.arguments || [])); - } + this._collapseAction = new Action('review.expand', nls.localize('label.collapse', "Collapse"), COLLAPSE_ACTION_CLASS, true, () => this.collapse()); + + this._actionbarWidget.push(this._collapseAction, { label: false, icon: true }); + } + + public collapse(): Promise { + if (this._commentThread.comments.length === 0) { + if ((this._commentThread as modes.CommentThread2).commentThreadHandle === undefined) { + this.dispose(); + return Promise.resolve(); + } else { + const deleteCommand = (this._commentThread as modes.CommentThread2).deleteCommand; + if (deleteCommand) { + return this.commandService.executeCommand(deleteCommand.id, ...(deleteCommand.arguments || [])); } } + } - this._isCollapsed = true; - this.hide(); - return Promise.resolve(); - }); - - this._actionbarWidget.push(this._collapseAction, { label: false, icon: true }); + this._isCollapsed = true; + this.hide(); + return Promise.resolve(); } public getGlyphPosition(): number { @@ -291,8 +293,10 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget // Move comment glyph widget and show position if the line has changed. const lineNumber = this._commentThread.range.startLineNumber; + let shouldMoveWidget = false; if (this._commentGlyph) { if (this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) { + shouldMoveWidget = true; this._commentGlyph.setLineNumber(lineNumber); } } @@ -301,7 +305,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this.createReplyButton(); } - if (!this._isCollapsed) { + if (shouldMoveWidget && !this._isCollapsed) { this.show({ lineNumber, column: 1 }, 2); } } @@ -319,11 +323,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } protected _onWidth(widthInPixel: number): void { - this._commentEditor.layout({ height: (this._commentEditor.hasWidgetFocus() ? 5 : 1) * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ }); + this._commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ }); } protected _doLayout(heightInPixel: number, widthInPixel: number): void { - this._commentEditor.layout({ height: (this._commentEditor.hasWidgetFocus() ? 5 : 1) * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ }); + this._commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ }); } display(lineNumber: number) { @@ -451,13 +455,15 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeRange(range => { // Move comment glyph widget and show position if the line has changed. const lineNumber = this._commentThread.range.startLineNumber; + let shouldMoveWidget = false; if (this._commentGlyph) { if (this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) { + shouldMoveWidget = true; this._commentGlyph.setLineNumber(lineNumber); } } - if (!this._isCollapsed) { + if (shouldMoveWidget && !this._isCollapsed) { this.show({ lineNumber, column: 1 }, 2); } })); @@ -997,6 +1003,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget hide() { this._isCollapsed = true; + // Focus the container so that the comment editor will be blurred before it is hidden + this.editor.focus(); super.hide(); } diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 7511f03fac9a..eedcd1341bea 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -16,7 +16,6 @@ import { IEditorContribution, IModelChangedEvent } from 'vs/editor/common/editor import { IRange, Range } from 'vs/editor/common/core/range'; import * as modes from 'vs/editor/common/modes'; import { peekViewResultsBackground, peekViewResultsSelectionBackground, peekViewTitleBackground } from 'vs/editor/contrib/referenceSearch/referencesWidget'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { editorForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -36,8 +35,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { ctxCommentEditorFocused, SimpleCommentEditor } from 'vs/workbench/contrib/comments/browser/simpleCommentEditor'; import { onUnexpectedError } from 'vs/base/common/errors'; -export const ctxCommentThreadVisible = new RawContextKey('commentThreadVisible', false); - export const ID = 'editor.contrib.review'; export class ReviewViewZone implements IViewZone { @@ -161,7 +158,6 @@ export class ReviewController implements IEditorContribution { private editor: ICodeEditor; private _newCommentWidget?: ReviewZoneWidget; private _commentWidgets: ReviewZoneWidget[]; - private _commentThreadVisible: IContextKey; private _commentInfos: ICommentInfo[]; private _commentingRangeDecorator: CommentingRangeDecorator; private mouseDownInfo: { lineNumber: number } | null = null; @@ -176,7 +172,6 @@ export class ReviewController implements IEditorContribution { constructor( editor: ICodeEditor, - @IContextKeyService readonly contextKeyService: IContextKeyService, @ICommentService private readonly commentService: ICommentService, @ICommandService private readonly _commandService: ICommandService, @INotificationService private readonly notificationService: INotificationService, @@ -193,7 +188,6 @@ export class ReviewController implements IEditorContribution { this._pendingNewCommentCache = {}; this._computePromise = null; - this._commentThreadVisible = ctxCommentThreadVisible.bindTo(contextKeyService); this._commentingRangeDecorator = new CommentingRangeDecorator(); this.globalToDispose.push(this.commentService.onDidDeleteDataProvider(ownerId => { @@ -458,7 +452,6 @@ export class ReviewController implements IEditorContribution { } // add new comment - this._commentThreadVisible.set(true); this._newCommentWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, ownerId, { extensionId: extensionId, threadId: null, @@ -687,8 +680,6 @@ export class ReviewController implements IEditorContribution { } public closeWidget(): void { - this._commentThreadVisible.reset(); - if (this._newCommentWidget) { this._newCommentWidget.dispose(); this._newCommentWidget = undefined; @@ -783,12 +774,17 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }); KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'closeReviewPanel', + id: 'workbench.action.hideComment', weight: KeybindingWeight.EditorContrib, primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], - when: ctxCommentThreadVisible, - handler: closeReviewPanel + when: ctxCommentEditorFocused, + handler: (accessor, args) => { + const activeCodeEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); + if (activeCodeEditor instanceof SimpleCommentEditor) { + activeCodeEditor.getParentThread().collapse(); + } + } }); export function getActiveEditor(accessor: ServicesAccessor): IActiveCodeEditor | null { @@ -809,21 +805,6 @@ export function getActiveEditor(accessor: ServicesAccessor): IActiveCodeEditor | return activeTextEditorWidget; } -function closeReviewPanel(accessor: ServicesAccessor, args: any) { - const outerEditor = getActiveEditor(accessor); - if (!outerEditor) { - return; - } - - const controller = ReviewController.get(outerEditor); - if (!controller) { - return; - } - - controller.closeWidget(); -} - - registerThemingParticipant((theme, collector) => { const peekViewBackground = theme.getColor(peekViewResultsBackground); if (peekViewBackground) { diff --git a/src/vs/workbench/contrib/comments/common/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/common/commentThreadWidget.ts index fc2215284cad..e5b025bd9907 100644 --- a/src/vs/workbench/contrib/comments/common/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/common/commentThreadWidget.ts @@ -5,4 +5,5 @@ export interface ICommentThreadWidget { submitComment: () => Promise; -} + collapse: () => void; +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 6586c3a5bebc..a78360a7124d 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { RunOnceScheduler, ignoreErrors } from 'vs/base/common/async'; +import { RunOnceScheduler, ignoreErrors, sequence } from 'vs/base/common/async'; import * as dom from 'vs/base/browser/dom'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IDebugService, State, IStackFrame, IDebugSession, IThread, CONTEXT_CALLSTACK_ITEM_TYPE, IDebugModel } from 'vs/workbench/contrib/debug/common/debug'; @@ -28,6 +28,7 @@ import { TreeResourceNavigator2, WorkbenchAsyncDataTree } from 'vs/platform/list import { onUnexpectedError } from 'vs/base/common/errors'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; +import { Event } from 'vs/base/common/event'; const $ = dom.$; @@ -45,6 +46,7 @@ export class CallStackView extends ViewletPanel { private dataSource: CallStackDataSource; private tree: WorkbenchAsyncDataTree; private contributedContextMenu: IMenu; + private parentSessionToExpand = new Set(); constructor( private options: IViewletViewOptions, @@ -80,7 +82,11 @@ export class CallStackView extends ViewletPanel { this.needsRefresh = false; this.dataSource.deemphasizedStackFramesToShow = []; - this.tree.updateChildren().then(() => this.updateTreeSelection()); + this.tree.updateChildren().then(() => { + this.parentSessionToExpand.forEach(s => this.tree.expand(s)); + this.parentSessionToExpand.clear(); + this.updateTreeSelection(); + }); }, 50); } @@ -193,7 +199,8 @@ export class CallStackView extends ViewletPanel { this.onCallStackChangeScheduler.schedule(); } })); - this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() => { + const onCallStackChange = Event.any(this.debugService.getViewModel().onDidFocusStackFrame, this.debugService.getViewModel().onDidFocusSession); + this.disposables.push(onCallStackChange(() => { if (this.ignoreFocusStackFrameEvent) { return; } @@ -216,6 +223,13 @@ export class CallStackView extends ViewletPanel { this.onCallStackChangeScheduler.schedule(); } })); + + this.disposables.push(this.debugService.onDidNewSession(s => { + if (s.parentSession) { + // Auto expand sessions that have sub sessions + this.parentSessionToExpand.add(s.parentSession); + } + })); } layoutBody(height: number, width: number): void { @@ -253,11 +267,20 @@ export class CallStackView extends ViewletPanel { updateSelectionAndReveal(session); } } else { - const expansionsPromise = ignoreErrors(this.tree.expand(thread.session)) - .then(() => ignoreErrors(this.tree.expand(thread))); - if (stackFrame) { - expansionsPromise.then(() => updateSelectionAndReveal(stackFrame)); + const expandPromises = [() => ignoreErrors(this.tree.expand(thread))]; + let s: IDebugSession | undefined = thread.session; + while (s) { + const sessionToExpand = s; + expandPromises.push(() => ignoreErrors(this.tree.expand(sessionToExpand))); + s = s.parentSession; } + + sequence(expandPromises.reverse()).then(() => { + const toReveal = stackFrame || session; + if (toReveal) { + updateSelectionAndReveal(toReveal); + } + }); } } @@ -574,7 +597,7 @@ class CallStackDataSource implements IAsyncDataSource 1) { + if (sessions.length > 1 || this.debugService.getViewModel().isMultiSessionView()) { return Promise.resolve(sessions.filter(s => !s.parentSession)); } diff --git a/src/vs/workbench/contrib/debug/browser/debugActionItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionItems.ts index 30a392a074e4..690db74e6361 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActionItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionItems.ts @@ -214,6 +214,10 @@ export class FocusSessionActionItem extends SelectActionItem { this.update(); } + protected getActionContext(_: string, index: number): any { + return this.debugService.getModel().getSessions()[index]; + } + private update() { const session = this.debugService.getViewModel().focusedSession; const sessions = this.getSessions(); diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index 3501bc6ad112..ec50afeded56 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -8,22 +8,14 @@ import { Action } from 'vs/base/common/actions'; import * as lifecycle from 'vs/base/common/lifecycle'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IDebugService, State, IDebugSession, IThread, IEnablement, IBreakpoint, IStackFrame, REPL_ID } - from 'vs/workbench/contrib/debug/common/debug'; -import { Variable, Expression, Thread, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IDebugService, State, IEnablement, IBreakpoint, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { Variable, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CollapseAction } from 'vs/workbench/browser/viewlet'; -import { first } from 'vs/base/common/arrays'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { memoize } from 'vs/base/common/decorators'; -import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { startDebugging } from 'vs/workbench/contrib/debug/common/debugUtils'; export abstract class AbstractDebugAction extends Action { @@ -135,23 +127,8 @@ export class StartAction extends AbstractDebugAction { this.toDispose.push(this.contextService.onDidChangeWorkbenchState(() => this.updateEnablement())); } - // Note: When this action is executed from the process explorer, a config is passed. For all - // other cases it is run with no arguments. - public run(): Promise { - const configurationManager = this.debugService.getConfigurationManager(); - let launch = configurationManager.selectedConfiguration.launch; - if (!launch || launch.getConfigurationNames().length === 0) { - const rootUri = this.historyService.getLastActiveWorkspaceRoot(); - launch = configurationManager.getLaunch(rootUri); - if (!launch || launch.getConfigurationNames().length === 0) { - const launches = configurationManager.getLaunches(); - launch = first(launches, l => !!(l && l.getConfigurationNames().length), launch); - } - - configurationManager.selectConfiguration(launch); - } - - return this.debugService.startDebugging(launch, undefined, this.isNoDebug()); + public run(): Promise { + return startDebugging(this.debugService, this.historyService, this.isNoDebug()); } protected isNoDebug(): boolean { @@ -204,242 +181,6 @@ export class SelectAndStartAction extends AbstractDebugAction { } } -export class RestartAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.restart'; - static LABEL = nls.localize('restartDebug', "Restart"); - static RECONNECT_LABEL = nls.localize('reconnectDebug', "Reconnect"); - - constructor(id: string, label: string, - @IDebugService debugService: IDebugService, - @IKeybindingService keybindingService: IKeybindingService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IHistoryService private readonly historyService: IHistoryService - ) { - super(id, label, 'debug-action restart', debugService, keybindingService, 70); - this.setLabel(this.debugService.getViewModel().focusedSession); - this.toDispose.push(this.debugService.getViewModel().onDidFocusSession(() => this.setLabel(this.debugService.getViewModel().focusedSession))); - } - - @memoize - private get startAction(): StartAction { - return new StartAction(StartAction.ID, StartAction.LABEL, this.debugService, this.keybindingService, this.contextService, this.historyService); - } - - private setLabel(session: IDebugSession | undefined): void { - if (session) { - this.updateLabel(session && session.configuration.request === 'attach' ? RestartAction.RECONNECT_LABEL : RestartAction.LABEL); - } - } - - public run(session: IDebugSession | undefined): Promise { - if (!session || !session.getId) { - session = this.debugService.getViewModel().focusedSession; - } - - if (!session) { - return this.startAction.run(); - } - - session.removeReplExpressions(); - return this.debugService.restartSession(session); - } - - protected isEnabled(state: State): boolean { - return super.isEnabled(state) && ( - state === State.Running || - state === State.Stopped || - StartAction.isEnabled(this.debugService) - ); - } -} - -export class StepOverAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.stepOver'; - static LABEL = nls.localize('stepOverDebug', "Step Over"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action step-over', debugService, keybindingService, 20); - } - - public run(thread: IThread | undefined): Promise { - if (!(thread instanceof Thread)) { - thread = this.debugService.getViewModel().focusedThread; - } - - return thread ? thread.next() : Promise.resolve(); - } - - protected isEnabled(state: State): boolean { - return super.isEnabled(state) && state === State.Stopped; - } -} - -export class StepIntoAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.stepInto'; - static LABEL = nls.localize('stepIntoDebug', "Step Into"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action step-into', debugService, keybindingService, 30); - } - - public run(thread: IThread | undefined): Promise { - if (!(thread instanceof Thread)) { - thread = this.debugService.getViewModel().focusedThread; - } - - return thread ? thread.stepIn() : Promise.resolve(); - } - - protected isEnabled(state: State): boolean { - return super.isEnabled(state) && state === State.Stopped; - } -} - -export class StepOutAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.stepOut'; - static LABEL = nls.localize('stepOutDebug', "Step Out"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action step-out', debugService, keybindingService, 40); - } - - public run(thread: IThread | undefined): Promise { - if (!(thread instanceof Thread)) { - thread = this.debugService.getViewModel().focusedThread; - } - - return thread ? thread.stepOut() : Promise.resolve(); - } - - protected isEnabled(state: State): boolean { - return super.isEnabled(state) && state === State.Stopped; - } -} - -export class StopAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.stop'; - static LABEL = nls.localize('stopDebug', "Stop"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action stop', debugService, keybindingService, 80); - } - - public run(session: IDebugSession | undefined): Promise { - if (!session || !session.getId) { - session = this.debugService.getViewModel().focusedSession; - } - - return this.debugService.stopSession(session); - } - - protected isEnabled(state: State): boolean { - return super.isEnabled(state) && (state !== State.Inactive); - } -} - -export class DisconnectAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.disconnect'; - static LABEL = nls.localize('disconnectDebug', "Disconnect"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action disconnect', debugService, keybindingService, 80); - } - - public run(): Promise { - const session = this.debugService.getViewModel().focusedSession; - return this.debugService.stopSession(session); - } - - protected isEnabled(state: State): boolean { - return super.isEnabled(state) && (state === State.Running || state === State.Stopped); - } -} - -export class ContinueAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.continue'; - static LABEL = nls.localize('continueDebug', "Continue"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action continue', debugService, keybindingService, 10); - } - - public run(thread: IThread | undefined): Promise { - if (!(thread instanceof Thread)) { - thread = this.debugService.getViewModel().focusedThread; - } - - return thread ? thread.continue() : Promise.resolve(); - } - - protected isEnabled(state: State): boolean { - return super.isEnabled(state) && state === State.Stopped; - } -} - -export class PauseAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.pause'; - static LABEL = nls.localize('pauseDebug', "Pause"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action pause', debugService, keybindingService, 10); - } - - public run(thread: IThread | undefined): Promise { - if (!(thread instanceof Thread)) { - thread = this.debugService.getViewModel().focusedThread; - if (!thread) { - const session = this.debugService.getViewModel().focusedSession; - const threads = session && session.getAllThreads(); - thread = threads && threads.length ? threads[0] : undefined; - } - } - - return thread ? thread.pause() : Promise.resolve(); - } - - protected isEnabled(state: State): boolean { - return super.isEnabled(state) && state === State.Running; - } -} - -export class TerminateThreadAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.terminateThread'; - static LABEL = nls.localize('terminateThread', "Terminate Thread"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, '', debugService, keybindingService); - } - - public run(thread: IThread | undefined): Promise { - if (!(thread instanceof Thread)) { - thread = this.debugService.getViewModel().focusedThread; - } - - return thread ? thread.terminate() : Promise.resolve(); - } - - protected isEnabled(state: State): boolean { - return super.isEnabled(state) && (state === State.Running || state === State.Stopped); - } -} - -export class RestartFrameAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.restartFrame'; - static LABEL = nls.localize('restartFrame', "Restart Frame"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, '', debugService, keybindingService); - } - - public run(frame: IStackFrame | undefined): Promise { - if (!frame) { - frame = this.debugService.getViewModel().focusedStackFrame; - } - - return frame!.restart(); - } -} - export class RemoveBreakpointAction extends AbstractDebugAction { static readonly ID = 'workbench.debug.viewlet.action.removeBreakpoint'; static LABEL = nls.localize('removeBreakpoint', "Remove Breakpoint"); @@ -575,29 +316,6 @@ export class AddFunctionBreakpointAction extends AbstractDebugAction { } } -export class SetValueAction extends AbstractDebugAction { - static readonly ID = 'workbench.debug.viewlet.action.setValue'; - static LABEL = nls.localize('setValue', "Set Value"); - - constructor(id: string, label: string, private variable: Variable, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, '', debugService, keybindingService); - } - - public run(): Promise { - if (this.variable instanceof Variable) { - this.debugService.getViewModel().setSelectedExpression(this.variable); - } - - return Promise.resolve(); - } - - protected isEnabled(state: State): boolean { - const session = this.debugService.getViewModel().focusedSession; - return !!(super.isEnabled(state) && state === State.Stopped && session && session.capabilities.supportsSetVariable); - } -} - - export class AddWatchExpressionAction extends AbstractDebugAction { static readonly ID = 'workbench.debug.viewlet.action.addWatchExpression'; static LABEL = nls.localize('addWatchExpression', "Add Expression"); @@ -617,53 +335,6 @@ export class AddWatchExpressionAction extends AbstractDebugAction { } } -export class EditWatchExpressionAction extends AbstractDebugAction { - static readonly ID = 'workbench.debug.viewlet.action.editWatchExpression'; - static LABEL = nls.localize('editWatchExpression', "Edit Expression"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, '', debugService, keybindingService); - } - - public run(expression: Expression): Promise { - this.debugService.getViewModel().setSelectedExpression(expression); - return Promise.resolve(); - } -} - -export class AddToWatchExpressionsAction extends AbstractDebugAction { - static readonly ID = 'workbench.debug.viewlet.action.addToWatchExpressions'; - static LABEL = nls.localize('addToWatchExpressions', "Add to Watch"); - - constructor(id: string, label: string, private variable: Variable, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action add-to-watch', debugService, keybindingService); - this.updateEnablement(); - } - - public run(): Promise { - this.debugService.addWatchExpression(this.variable.evaluateName); - return Promise.resolve(undefined); - } - - protected isEnabled(state: State): boolean { - return super.isEnabled(state) && this.variable && !!this.variable.evaluateName; - } -} - -export class RemoveWatchExpressionAction extends AbstractDebugAction { - static readonly ID = 'workbench.debug.viewlet.action.removeWatchExpression'; - static LABEL = nls.localize('removeWatchExpression', "Remove Expression"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, '', debugService, keybindingService); - } - - public run(expression: Expression): Promise { - this.debugService.removeWatchExpressions(expression.getId()); - return Promise.resolve(); - } -} - export class RemoveAllWatchExpressionsAction extends AbstractDebugAction { static readonly ID = 'workbench.debug.viewlet.action.removeAllWatchExpressions'; static LABEL = nls.localize('removeAllWatchExpressions', "Remove All Expressions"); @@ -683,53 +354,6 @@ export class RemoveAllWatchExpressionsAction extends AbstractDebugAction { } } -export class ToggleReplAction extends TogglePanelAction { - static readonly ID = 'workbench.debug.action.toggleRepl'; - static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugConsoleAction' }, 'Debug Console'); - private toDispose: lifecycle.IDisposable[]; - - constructor(id: string, label: string, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @IPanelService panelService: IPanelService - ) { - super(id, label, REPL_ID, panelService, layoutService, 'debug-action toggle-repl'); - this.toDispose = []; - this.registerListeners(); - } - - private registerListeners(): void { - this.toDispose.push(this.panelService.onDidPanelOpen(({ panel }) => { - if (panel.getId() === REPL_ID) { - this.class = 'debug-action toggle-repl'; - this.tooltip = ToggleReplAction.LABEL; - } - })); - } - - public dispose(): void { - super.dispose(); - this.toDispose = lifecycle.dispose(this.toDispose); - } -} - -export class FocusReplAction extends Action { - - static readonly ID = 'workbench.debug.action.focusRepl'; - static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, 'Focus on Debug Console View'); - - - constructor(id: string, label: string, - @IPanelService private readonly panelService: IPanelService - ) { - super(id, label); - } - - public run(): Promise { - this.panelService.openPanel(REPL_ID, true); - return Promise.resolve(); - } -} - export class FocusSessionAction extends AbstractDebugAction { static readonly ID = 'workbench.action.debug.focusProcess'; static LABEL = nls.localize('focusSession', "Focus Session"); @@ -742,8 +366,7 @@ export class FocusSessionAction extends AbstractDebugAction { super(id, label, '', debugService, keybindingService, 100); } - public run(sessionName: string): Promise { - const session = this.debugService.getModel().getSessions().filter(p => p.getLabel() === sessionName).pop(); + public run(session: IDebugSession): Promise { this.debugService.focusStackFrame(undefined, undefined, session, true); const stackFrame = this.debugService.getViewModel().focusedStackFrame; if (stackFrame) { @@ -754,65 +377,6 @@ export class FocusSessionAction extends AbstractDebugAction { } } -// Actions used by the chakra debugger -export class StepBackAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.stepBack'; - static LABEL = nls.localize('stepBackDebug', "Step Back"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action step-back', debugService, keybindingService, 50); - } - - public run(thread: IThread | undefined): Promise { - if (!(thread instanceof Thread)) { - thread = this.debugService.getViewModel().focusedThread; - } - - return thread ? thread.stepBack() : Promise.resolve(); - } - - protected isEnabled(state: State): boolean { - const session = this.debugService.getViewModel().focusedSession; - return !!(super.isEnabled(state) && state === State.Stopped && - session && session.capabilities.supportsStepBack); - } -} - -export class ReverseContinueAction extends AbstractDebugAction { - static readonly ID = 'workbench.action.debug.reverseContinue'; - static LABEL = nls.localize('reverseContinue', "Reverse"); - - constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, 'debug-action reverse-continue', debugService, keybindingService, 60); - } - - public run(thread: IThread | undefined): Promise { - if (!(thread instanceof Thread)) { - thread = this.debugService.getViewModel().focusedThread; - } - - return thread ? thread.reverseContinue() : Promise.resolve(); - } - - protected isEnabled(state: State): boolean { - const session = this.debugService.getViewModel().focusedSession; - return !!(super.isEnabled(state) && state === State.Stopped && - session && session.capabilities.supportsStepBack); - } -} - -export class ReplCollapseAllAction extends CollapseAction { - constructor(tree: AsyncDataTree, private toFocus: { focus(): void; }) { - super(tree, true, undefined); - } - - public run(event?: any): Promise { - return super.run(event).then(() => { - this.toFocus.focus(); - }); - } -} - export class CopyValueAction extends Action { static readonly ID = 'workbench.debug.viewlet.action.copyValue'; static LABEL = nls.localize('copyValue', "Copy Value"); @@ -840,57 +404,3 @@ export class CopyValueAction extends Action { return Promise.resolve(undefined); } } - -export class CopyEvaluatePathAction extends Action { - static readonly ID = 'workbench.debug.viewlet.action.copyEvaluatePath'; - static LABEL = nls.localize('copyAsExpression', "Copy as Expression"); - - constructor( - id: string, label: string, private value: Variable, - @IClipboardService private readonly clipboardService: IClipboardService - ) { - super(id, label); - this._enabled = this.value && !!this.value.evaluateName; - } - - public run(): Promise { - this.clipboardService.writeText(this.value.evaluateName!); - return Promise.resolve(undefined); - } -} - -export class CopyAction extends Action { - static readonly ID = 'workbench.debug.action.copy'; - static LABEL = nls.localize('copy', "Copy"); - - constructor( - id: string, label: string, - @IClipboardService private readonly clipboardService: IClipboardService - ) { - super(id, label); - } - - public run(): Promise { - this.clipboardService.writeText(window.getSelection().toString()); - return Promise.resolve(undefined); - } -} - -export class CopyStackTraceAction extends Action { - static readonly ID = 'workbench.action.debug.copyStackTrace'; - static LABEL = nls.localize('copyStackTrace', "Copy Call Stack"); - - constructor( - id: string, label: string, - @IClipboardService private readonly clipboardService: IClipboardService, - @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService - ) { - super(id, label); - } - - public run(frame: IStackFrame): Promise { - const eol = this.textResourcePropertiesService.getEOL(frame.source.uri); - this.clipboardService.writeText(frame.thread.getCallStack().map(sf => sf.toString()).join(eol)); - return Promise.resolve(undefined); - } -} \ No newline at end of file diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index e2592e71f9a7..54bbbc42ad11 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -149,7 +149,19 @@ export function registerCommands(): void { primary: KeyCode.F6, when: CONTEXT_DEBUG_STATE.isEqualTo('running'), handler: (accessor: ServicesAccessor, _: string, thread: IThread | undefined) => { - getThreadAndRun(accessor, thread, thread => thread.pause()); + const debugService = accessor.get(IDebugService); + if (!(thread instanceof Thread)) { + thread = debugService.getViewModel().focusedThread; + if (!thread) { + const session = debugService.getViewModel().focusedSession; + const threads = session && session.getAllThreads(); + thread = threads && threads.length ? threads[0] : undefined; + } + } + + if (thread) { + thread.pause().then(undefined, onUnexpectedError); + } } }); diff --git a/src/vs/workbench/contrib/debug/electron-browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/electron-browser/debug.contribution.ts index 0cdcf5bc9630..9a748a6d9419 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debug.contribution.ts @@ -277,7 +277,7 @@ const registerDebugToolBarItem = (id: string, title: string, icon: string, order }; registerDebugToolBarItem(CONTINUE_ID, continueLabel, 'continue', 10, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugToolBarItem(PAUSE_ID, pauseLabel, 'pause', 10, CONTEXT_DEBUG_STATE.notEqualsTo('stopped'), CONTEXT_DEBUG_STATE.isEqualTo('running')); +registerDebugToolBarItem(PAUSE_ID, pauseLabel, 'pause', 10, CONTEXT_DEBUG_STATE.notEqualsTo('stopped')); registerDebugToolBarItem(STOP_ID, stopLabel, 'stop', 70, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated()); registerDebugToolBarItem(DISCONNECT_ID, disconnectLabel, 'disconnect', 70, CONTEXT_FOCUSED_SESSION_IS_ATTACH); registerDebugToolBarItem(STEP_OVER_ID, stepOverLabel, 'step-over', 20, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); diff --git a/src/vs/workbench/contrib/debug/electron-browser/debugService.ts b/src/vs/workbench/contrib/debug/electron-browser/debugService.ts index b74a6d4aa488..1b889f6c5693 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugService.ts @@ -524,6 +524,7 @@ export class DebugService implements IDebugService { ); } session.shutdown(); + this.endInitializingState(); this._onDidEndSession.fire(session); const focusedSession = this.viewModel.focusedSession; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts index c27981f8bb6e..59ca4c231f9c 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts @@ -34,7 +34,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { INotificationService } from 'vs/platform/notification/common/notification'; import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { distinct } from 'vs/base/common/arrays'; +import { distinct, coalesce } from 'vs/base/common/arrays'; import { IExperimentService, IExperiment, ExperimentActionType } from 'vs/workbench/contrib/experiments/node/experimentService'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; @@ -118,7 +118,7 @@ export class ExtensionsListView extends ViewletPanel { horizontalScrolling: false }) as WorkbenchPagedList; this.list.onContextMenu(e => this.onContextMenu(e), this, this.disposables); - this.list.onFocusChange(e => extensionsViewState.onFocusChange(e.elements), this, this.disposables); + this.list.onFocusChange(e => extensionsViewState.onFocusChange(coalesce(e.elements)), this, this.disposables); this.disposables.push(this.list); this.disposables.push(extensionsViewState); diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 877be219681b..fa22b6587f19 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -42,7 +42,7 @@ import { Constants } from 'vs/editor/common/core/uint'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { coalesce } from 'vs/base/common/arrays'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; +import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { onUnexpectedError } from 'vs/base/common/errors'; import { sequence } from 'vs/base/common/async'; @@ -100,7 +100,6 @@ export class BaseErrorReportingAction extends Action { } } -const PLACEHOLDER_URI = URI.file(''); function refreshIfSeparator(value: string, explorerService: IExplorerService): void { if (value && ((value.indexOf('/') >= 0) || (value.indexOf('\\') >= 0))) { // New input contains separator, multiple resources will get created workaround for #68204 @@ -143,7 +142,7 @@ export class NewFileAction extends BaseErrorReportingAction { return Promise.reject(new Error('Parent folder is readonly.')); } - const stat = new ExplorerItem(PLACEHOLDER_URI, folder, false); + const stat = new NewExplorerItem(folder, false); return folder.fetchChildren(this.fileService, this.explorerService).then(() => { folder.addChild(stat); @@ -211,7 +210,7 @@ export class NewFolderAction extends BaseErrorReportingAction { return Promise.reject(new Error('Parent folder is readonly.')); } - const stat = new ExplorerItem(PLACEHOLDER_URI, folder, true); + const stat = new NewExplorerItem(folder, true); return folder.fetchChildren(this.fileService, this.explorerService).then(() => { folder.addChild(stat); diff --git a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts index fdb5c3c3c5ff..d53674a16a25 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts @@ -36,10 +36,6 @@ export class ExplorerDecorationsProvider implements IDecorationsProvider { return this._onDidChange.event; } - changed(uris: URI[]): void { - this._onDidChange.fire(uris); - } - provideDecorations(resource: URI): IDecorationData | undefined { const fileStat = this.explorerService.findClosest(resource); if (fileStat && fileStat.isRoot && fileStat.isError) { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 3bb202bba824..7b086699ab81 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -37,7 +37,7 @@ import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; +import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels'; import { createFileIconThemableTreeContainerScope } from 'vs/workbench/browser/parts/views/views'; @@ -45,9 +45,6 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { isMacintosh } from 'vs/base/common/platform'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { isEqualOrParent } from 'vs/base/common/resources'; import { values } from 'vs/base/common/map'; import { first } from 'vs/base/common/arrays'; @@ -68,7 +65,6 @@ export class ExplorerView extends ViewletPanel { // Refresh is needed on the initial explorer open private shouldRefresh = true; private dragHandler: DelayedDragHandler; - private decorationProvider: ExplorerDecorationsProvider; private autoReveal = false; constructor( @@ -99,9 +95,9 @@ export class ExplorerView extends ViewletPanel { this.readonlyContext = ExplorerResourceReadonlyContext.bindTo(contextKeyService); this.rootContext = ExplorerRootContext.bindTo(contextKeyService); - this.decorationProvider = new ExplorerDecorationsProvider(this.explorerService, contextService); - decorationService.registerDecorationsProvider(this.decorationProvider); - this.disposables.push(this.decorationProvider); + const decorationProvider = new ExplorerDecorationsProvider(this.explorerService, contextService); + decorationService.registerDecorationsProvider(decorationProvider); + this.disposables.push(decorationProvider); this.disposables.push(this.resourceContext); } @@ -285,7 +281,13 @@ export class ExplorerView extends ViewletPanel { accessibilityProvider: new ExplorerAccessibilityProvider(), ariaLabel: nls.localize('treeAriaLabel', "Files Explorer"), identityProvider: { - getId: (stat: ExplorerItem) => stat.resource + getId: (stat: ExplorerItem) => { + if (stat instanceof NewExplorerItem) { + return `new:${stat.resource}`; + } + + return stat.resource; + } }, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (stat: ExplorerItem) => { @@ -338,17 +340,6 @@ export class ExplorerView extends ViewletPanel { })); this.disposables.push(this.tree.onContextMenu(e => this.onContextMenu(e))); - this.disposables.push(this.tree.onKeyDown(e => { - const event = new StandardKeyboardEvent(e); - const toggleCollapsed = isMacintosh ? (event.keyCode === KeyCode.DownArrow && event.metaKey) : event.keyCode === KeyCode.Enter; - if (toggleCollapsed && !this.explorerService.isEditable(undefined)) { - const focus = this.tree.getFocus(); - if (focus.length === 1 && focus[0].isDirectory) { - this.tree.toggleCollapsed(focus[0]); - } - } - })); - // save view state on shutdown this.storageService.onWillSaveState(() => { diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 33610cf3e354..5c8d1cf2ad3a 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -222,7 +222,8 @@ export class FilesRenderer implements ITreeRenderer editableData.onFinish(value, success), 0); }); let ignoreDisposeAndBlur = true; @@ -252,7 +253,7 @@ export class FilesRenderer implements ITreeRenderer { if (!ignoreDisposeAndBlur) { blurDisposable.dispose(); - done(inputBox.isInputValid()); + done(false); } }); } @@ -781,7 +782,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { rootsToMove.push(data); } } - if (!targetIndex) { + if (targetIndex === undefined) { targetIndex = workspaceCreationData.length; } @@ -804,6 +805,10 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Otherwise move const targetResource = joinPath(target.resource, source.name); + if (source.isReadonly) { + // Do not allow moving readonly items + return Promise.resolve(); + } return this.textFileService.move(source.resource, targetResource).then(undefined, error => { diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index a6b96c3614e7..8999b4a54fb3 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -367,3 +367,9 @@ export class ExplorerItem { return null; } } + +export class NewExplorerItem extends ExplorerItem { + constructor(parent: ExplorerItem, isDirectory: boolean) { + super(URI.file(''), parent, isDirectory); + } +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/markers/browser/markers.ts b/src/vs/workbench/contrib/markers/browser/markers.ts index a044359aa991..eb461bf86f16 100644 --- a/src/vs/workbench/contrib/markers/browser/markers.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.ts @@ -69,8 +69,8 @@ export class ActivityUpdater extends Disposable implements IWorkbenchContributio } private updateBadge(): void { - const { errors, warnings, infos, unknowns } = this.markerService.getStatistics(); - const total = errors + warnings + infos + unknowns; + const { errors, warnings, infos } = this.markerService.getStatistics(); + const total = errors + warnings + infos; const message = localize('totalProblems', 'Total {0} Problems', total); this.activityService.showActivity(Constants.MARKERS_PANEL_ID, new NumberBadge(total, () => message)); } diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts index c98836240fd5..8276b5d74da4 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts @@ -91,7 +91,6 @@ export class OpenGlobalSettingsAction extends Action { export class OpenRemoteSettingsAction extends Action { static readonly ID = 'workbench.action.openRemoteSettings'; - static readonly LABEL = nls.localize('openRemoteSettings', "Open User Settings (Remote)"); constructor( id: string, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 4aee6e0fcccb..a6913d2067b0 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -1220,7 +1220,16 @@ export class SettingsTreeFilter implements ITreeFilter { } class SettingsTreeDelegate implements IListVirtualDelegate { - getHeight(element: SettingsTreeElement): number { + + private heightCache = new WeakMap(); + + getHeight(element: SettingsTreeGroupChild): number { + const cachedHeight = this.heightCache.get(element); + + if (typeof cachedHeight === 'number') { + return cachedHeight; + } + if (element instanceof SettingsTreeGroupElement) { if (element.isFirstGroup) { return 31; @@ -1273,6 +1282,10 @@ class SettingsTreeDelegate implements IListVirtualDelegate extends ObjectTreeModel { diff --git a/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts index e967c56e796a..58eda7608793 100644 --- a/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts @@ -38,6 +38,10 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { DefaultPreferencesEditorInput, KeybindingsEditorInput, PreferencesEditorInput, SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { ExplorerRootContext, ExplorerFolderContext } from 'vs/workbench/contrib/files/common/files'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); @@ -385,7 +389,10 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon constructor( @IEnvironmentService environmentService: IEnvironmentService, @IPreferencesService private readonly preferencesService: IPreferencesService, - @IWorkspaceContextService private readonly workpsaceContextService: IWorkspaceContextService + @IWorkspaceContextService private readonly workpsaceContextService: IWorkspaceContextService, + @ILabelService labelService: ILabelService, + @IExtensionService extensionService: IExtensionService, + @IWindowService windowService: IWindowService ) { super(); MenuRegistry.appendMenuItem(MenuId.EditorTitle, { @@ -418,10 +425,27 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon order: 1 }); - this.updatePreferencesEditorMenuItem(); this._register(workpsaceContextService.onDidChangeWorkbenchState(() => this.updatePreferencesEditorMenuItem())); this._register(workpsaceContextService.onDidChangeWorkspaceFolders(() => this.updatePreferencesEditorMenuItemForWorkspaceFolders())); + + extensionService.whenInstalledExtensionsRegistered() + .then(() => { + const remoteAuthority = windowService.getConfiguration().remoteAuthority; + const hostLabel = labelService.getHostLabel(REMOTE_HOST_SCHEME, remoteAuthority) || remoteAuthority; + const label = nls.localize('openRemoteSettings', "Open User Settings ({0})", hostLabel); + CommandsRegistry.registerCommand(OpenRemoteSettingsAction.ID, serviceAccessor => { + serviceAccessor.get(IInstantiationService).createInstance(OpenRemoteSettingsAction, OpenRemoteSettingsAction.ID, label).run(); + }); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: OpenRemoteSettingsAction.ID, + title: { value: label, original: `Preferences: Open User Settings (${hostLabel})` }, + category: nls.localize('preferencesCategory', "Preferences") + }, + when: IsRemoteContext + }); + }); } private updatePreferencesEditorMenuItem() { @@ -566,18 +590,6 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { group: '1_keyboard_preferences_actions' }); -CommandsRegistry.registerCommand(OpenRemoteSettingsAction.ID, serviceAccessor => { - serviceAccessor.get(IInstantiationService).createInstance(OpenRemoteSettingsAction, OpenRemoteSettingsAction.ID, OpenRemoteSettingsAction.LABEL).run(); -}); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: OpenRemoteSettingsAction.ID, - title: { value: OpenRemoteSettingsAction.LABEL, original: 'Preferences: Open Remote Settings' }, - category: nls.localize('preferencesCategory', "Preferences") - }, - when: IsRemoteContext -}); - abstract class SettingsCommand extends Command { protected getPreferencesEditor(accessor: ServicesAccessor): PreferencesEditor | SettingsEditor2 | null { diff --git a/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts index 5dfeda249009..9a6a9f94be38 100644 --- a/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts @@ -1384,6 +1384,8 @@ class TaskService extends Disposable implements ITaskService { return Promise.all([this.extensionService.activateByEvent('onCommand:workbench.action.tasks.runTask'), TaskDefinitionRegistry.onReady()]).then(() => { let validTypes: IStringDictionary = Object.create(null); TaskDefinitionRegistry.all().forEach(definition => validTypes[definition.taskType] = true); + validTypes['shell'] = true; + validTypes['process'] = true; return new Promise(resolve => { let result: TaskSet[] = []; let counter: number = 0; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 8bb540269ebd..c5f80bdbb74e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -426,16 +426,23 @@ export class TerminalInstance implements ITerminalInstance { this._xterm.on('key', (key, ev) => this._onKey(key, ev)); if (this._processManager) { - if (this._processManager.os === platform.OperatingSystem.Windows) { - this._xterm.winptyCompatInit(); - } this._processManager.onProcessData(data => this._onProcessData(data)); this._xterm.on('data', data => this._processManager!.write(data)); // TODO: How does the cwd work on detached processes? - this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, platform.platform, this._processManager); this.processReady.then(async () => { this._linkHandler.processCwd = await this._processManager!.getInitialCwd(); }); + // Init winpty compat and link handler after process creation as they rely on the + // underlying process OS + this._processManager.onProcessReady(() => { + if (!this._processManager) { + return; + } + if (this._processManager.os === platform.OperatingSystem.Windows) { + this._xterm.winptyCompatInit(); + } + this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, platform.platform, this._processManager); + }); } this._xterm.on('focus', () => this._onFocus.fire(this)); @@ -579,7 +586,7 @@ export class TerminalInstance implements ITerminalInstance { if (this._processManager) { this._widgetManager = new TerminalWidgetManager(this._wrapperElement); - this._linkHandler.setWidgetManager(this._widgetManager); + this._processManager.onProcessReady(() => this._linkHandler.setWidgetManager(this._widgetManager)); } const computedStyle = window.getComputedStyle(this._container); diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 29d04dd611db..e8cec3e367f7 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -12,7 +12,7 @@ import { ElectronWindow } from 'vs/workbench/electron-browser/window'; import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser'; import { domContentLoaded, addDisposableListener, EventType, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { isLinux, isMacintosh, isWindows, OperatingSystem } from 'vs/base/common/platform'; +import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { URI as uri } from 'vs/base/common/uri'; import { WorkspaceService, DefaultConfigurationExportHelper } from 'vs/workbench/services/configuration/node/configurationService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -48,6 +48,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/workbench/services/files2/electron-browser/diskFileSystemProvider'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME, RemoteExtensionsFileSystemProvider } from 'vs/platform/remote/common/remoteAgentFileSystemChannel'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; class CodeRendererMain extends Disposable { @@ -177,27 +178,23 @@ class CodeRendererMain extends Disposable { const logService = this._register(this.createLogService(mainProcessService, environmentService)); serviceCollection.set(ILogService, logService); - // Files - const fileService = new FileService2(logService); - serviceCollection.set(IFileService, fileService); - - fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(logService)); - // Remote const remoteAuthorityResolverService = new RemoteAuthorityResolverService(); serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService); + const remoteAgentService = new RemoteAgentService(this.configuration, environmentService, remoteAuthorityResolverService); serviceCollection.set(IRemoteAgentService, remoteAgentService); + // Files + const fileService = new FileService2(logService); + serviceCollection.set(IFileService, fileService); + + fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(logService)); + const connection = remoteAgentService.getConnection(); if (connection) { const channel = connection.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME); - const fileSystemProvider = new RemoteExtensionsFileSystemProvider(channel); - fileService.registerProvider('vscode-remote', fileSystemProvider); - remoteAgentService.getEnvironment().then(remoteAgentEnvironment => { - const isCaseSensitive = !!(remoteAgentEnvironment && remoteAgentEnvironment.os === OperatingSystem.Linux); - fileSystemProvider.setCaseSensitive(isCaseSensitive); - }); + fileService.registerProvider(REMOTE_HOST_SCHEME, new RemoteExtensionsFileSystemProvider(channel, remoteAgentService.getEnvironment())); } return this.resolveWorkspaceInitializationPayload(environmentService).then(payload => Promise.all([ diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts index 235f97ec3820..625e38c1d245 100644 --- a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -85,7 +85,8 @@ export class FileDialogService implements IFileDialogService { } private shouldUseSimplified(schema: string): boolean { - return (schema !== Schemas.file) || (this.configurationService.getValue('workbench.dialogs.useSimplified') === 'true'); + const setting = this.configurationService.getValue('workbench.dialogs.useSimplified'); + return (schema !== Schemas.file) || ((setting === 'true') || (setting === true)); } private ensureFileSchema(schema: string): string[] { diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 686a6ece7c3c..08e94b297eaf 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -34,6 +34,7 @@ import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } import { Schemas } from 'vs/base/common/network'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; +import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; const hasOwnProperty = Object.hasOwnProperty; const NO_OP_VOID_PROMISE = Promise.resolve(undefined); @@ -843,7 +844,16 @@ export class ExtensionService extends Disposable implements IExtensionService { } public _onExtensionHostExit(code: number): void { - ipc.send('vscode:exit', code); + // Expected development extension termination: When the extension host goes down we also shutdown the window + const devOpts = parseExtensionDevOptions(this._environmentService); + if (!devOpts.isExtensionDevTestFromCli) { + this._windowService.closeWindow(); + } + + // When CLI testing make sure to exit with proper exit code + else { + ipc.send('vscode:exit', code); + } } } diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index de85cbb55f8e..25989b5f9404 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -287,7 +287,7 @@ function createPatchedModules(configProvider: ExtHostConfigProvider, resolveProx }; configProvider.onDidChangeConfiguration(e => { certSetting.config = !!configProvider.getConfiguration('http') - .get('systemCertificates'); + .get('systemCertificates'); }); return { @@ -332,9 +332,10 @@ function patches(originals: typeof http | typeof https, resolveProxy: ReturnType return original.apply(null, arguments as unknown as any[]); } + const optionsPatched = options.agent instanceof ProxyAgent; const config = onRequest && ((options)._vscodeProxySupport || /* LS */ (options)._vscodeSystemProxy) || proxySetting.config; - const useProxySettings = (config === 'override' || config === 'on' && !options.agent) && !(options.agent instanceof ProxyAgent); - const useSystemCertificates = certSetting.config && originals === https && !(options as https.RequestOptions).ca; + const useProxySettings = !optionsPatched && (config === 'override' || config === 'on' && !options.agent); + const useSystemCertificates = !optionsPatched && certSetting.config && originals === https && !(options as https.RequestOptions).ca; if (useProxySettings || useSystemCertificates) { if (url) { diff --git a/src/vs/workbench/services/files2/common/fileService2.ts b/src/vs/workbench/services/files2/common/fileService2.ts index 6ce7ce534b8b..f0c271b226ca 100644 --- a/src/vs/workbench/services/files2/common/fileService2.ts +++ b/src/vs/workbench/services/files2/common/fileService2.ts @@ -206,7 +206,7 @@ export class FileService2 extends Disposable implements IFileService { }); } - private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise { + private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat | { type: FileType } & Partial, siblings: number | undefined, resolveMetadata: boolean, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise { // convert to file stat const fileStat: IFileStat = { @@ -355,11 +355,11 @@ export class FileService2 extends Disposable implements IFileService { const targetProvider = this.throwIfFileSystemIsReadonly(await this.withProvider(target)); // move - await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'move', overwrite); + const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'move', overwrite); // resolve and send events const fileStat = await this.resolveFile(target, { resolveMetadata: true }); - this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.MOVE, fileStat)); + this._onAfterOperation.fire(new FileOperationEvent(source, mode === 'move' ? FileOperation.MOVE : FileOperation.COPY, fileStat)); return fileStat; } @@ -369,16 +369,16 @@ export class FileService2 extends Disposable implements IFileService { const targetProvider = this.throwIfFileSystemIsReadonly(await this.withProvider(target)); // copy - await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', overwrite); + const mode = await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', overwrite); // resolve and send events const fileStat = await this.resolveFile(target, { resolveMetadata: true }); - this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.COPY, fileStat)); + this._onAfterOperation.fire(new FileOperationEvent(source, mode === 'copy' ? FileOperation.COPY : FileOperation.MOVE, fileStat)); return fileStat; } - private async doMoveCopy(sourceProvider: IFileSystemProvider, source: URI, targetProvider: IFileSystemProvider, target: URI, mode: 'move' | 'copy', overwrite?: boolean): Promise { + private async doMoveCopy(sourceProvider: IFileSystemProvider, source: URI, targetProvider: IFileSystemProvider, target: URI, mode: 'move' | 'copy', overwrite?: boolean): Promise<'move' | 'copy'> { // validation const { exists, isCaseChange } = await this.doValidateMoveCopy(sourceProvider, source, targetProvider, target, overwrite); @@ -396,7 +396,7 @@ export class FileService2 extends Disposable implements IFileService { // same provider with fast copy: leverage copy() functionality if (sourceProvider === targetProvider && hasFileFolderCopyCapability(sourceProvider)) { - return sourceProvider.copy(source, target, { overwrite: !!overwrite }); + return sourceProvider.copy(source, target, { overwrite: !!overwrite }).then(() => mode); } // otherwise, ensure we got the capabilities to do this @@ -411,9 +411,9 @@ export class FileService2 extends Disposable implements IFileService { // traverse the source if it is a folder and not a file const sourceFile = await this.resolveFile(source); if (sourceFile.isDirectory) { - return this.doCopyFolder(sourceProvider, sourceFile, targetProvider, target, overwrite); + return this.doCopyFolder(sourceProvider, sourceFile, targetProvider, target, overwrite).then(() => mode); } else { - return this.doCopyFile(sourceProvider, source, targetProvider, target, overwrite); + return this.doCopyFile(sourceProvider, source, targetProvider, target, overwrite).then(() => mode); } } @@ -422,14 +422,14 @@ export class FileService2 extends Disposable implements IFileService { // same provider: leverage rename() functionality if (sourceProvider === targetProvider) { - return sourceProvider.rename(source, target, { overwrite: !!overwrite }); + return sourceProvider.rename(source, target, { overwrite: !!overwrite }).then(() => mode); } // across providers: copy to target & delete at source else { await this.doMoveCopy(sourceProvider, source, targetProvider, target, 'copy', overwrite); - return this.del(source, { recursive: true }); + return this.del(source, { recursive: true }).then(() => 'copy' as 'move' | 'copy'); } } } diff --git a/src/vs/workbench/services/files2/node/diskFileSystemProvider.ts b/src/vs/workbench/services/files2/node/diskFileSystemProvider.ts index 888cf34a0c38..0616ab0e9a5c 100644 --- a/src/vs/workbench/services/files2/node/diskFileSystemProvider.ts +++ b/src/vs/workbench/services/files2/node/diskFileSystemProvider.ts @@ -12,11 +12,12 @@ import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { statLink, readdir, unlink, del, move, copy, readFile, writeFile, fileExists, truncate } from 'vs/base/node/pfs'; -import { normalize } from 'vs/base/common/path'; +import { normalize, basename, dirname } from 'vs/base/common/path'; import { joinPath } from 'vs/base/common/resources'; import { isEqual } from 'vs/base/common/extpath'; import { retry } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; +import { localize } from 'vs/nls'; export class DiskFileSystemProvider extends Disposable implements IFileSystemProvider { @@ -113,9 +114,9 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro // Validate target const exists = await fileExists(filePath); if (exists && !opts.overwrite) { - throw createFileSystemProviderError(new Error('File already exists'), FileSystemProviderErrorCode.FileExists); + throw createFileSystemProviderError(new Error(localize('fileExists', "File already exists")), FileSystemProviderErrorCode.FileExists); } else if (!exists && !opts.create) { - throw createFileSystemProviderError(new Error('File does not exist'), FileSystemProviderErrorCode.FileNotFound); + throw createFileSystemProviderError(new Error(localize('fileNotExists', "File does not exist")), FileSystemProviderErrorCode.FileNotFound); } if (exists && isWindows) { @@ -241,9 +242,10 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro } async rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise { + const fromFilePath = this.toFilePath(from); + const toFilePath = this.toFilePath(to); + try { - const fromFilePath = this.toFilePath(from); - const toFilePath = this.toFilePath(to); // Ensure target does not exist await this.validateTargetDeleted(from, to, opts && opts.overwrite); @@ -251,14 +253,22 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro // Move await move(fromFilePath, toFilePath); } catch (error) { + + // rewrite some typical errors that can happen especially around symlinks + // to something the user can better understand + if (error.code === 'EINVAL' || error.code === 'EBUSY' || error.code === 'ENAMETOOLONG') { + error = new Error(localize('moveError', "Unable to move '{0}' into '{1}' ({2}).", basename(fromFilePath), basename(dirname(toFilePath)), error.toString())); + } + throw this.toFileSystemProviderError(error); } } async copy(from: URI, to: URI, opts: FileOverwriteOptions): Promise { + const fromFilePath = this.toFilePath(from); + const toFilePath = this.toFilePath(to); + try { - const fromFilePath = this.toFilePath(from); - const toFilePath = this.toFilePath(to); // Ensure target does not exist await this.validateTargetDeleted(from, to, opts && opts.overwrite); @@ -266,6 +276,13 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro // Copy await copy(fromFilePath, toFilePath); } catch (error) { + + // rewrite some typical errors that can happen especially around symlinks + // to something the user can better understand + if (error.code === 'EINVAL' || error.code === 'EBUSY' || error.code === 'ENAMETOOLONG') { + error = new Error(localize('copyError', "Unable to copy '{0}' into '{1}' ({2}).", basename(fromFilePath), basename(dirname(toFilePath)), error.toString())); + } + throw this.toFileSystemProviderError(error); } } diff --git a/src/vs/workbench/services/files2/test/node/diskFileService.test.ts b/src/vs/workbench/services/files2/test/node/diskFileService.test.ts index ab56c897a0fc..f80637ad56bb 100644 --- a/src/vs/workbench/services/files2/test/node/diskFileService.test.ts +++ b/src/vs/workbench/services/files2/test/node/diskFileService.test.ts @@ -12,7 +12,7 @@ import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { generateUuid } from 'vs/base/common/uuid'; import { join, basename, dirname, posix } from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; -import { copy, del } from 'vs/base/node/pfs'; +import { copy, del, symlink } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { existsSync, statSync, readdirSync, readFileSync } from 'fs'; import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; @@ -313,11 +313,11 @@ suite('Disk File Service', () => { test('resolveFile - folder symbolic link', async () => { if (isWindows) { - return; // only for unix systems + return; // not happy } const link = URI.file(join(testDir, 'deep-link')); - await promisify(exec)(`ln -s deep ${basename(link.fsPath)}`, { cwd: testDir }); + await symlink(join(testDir, 'deep'), link.fsPath); const resolved = await service.resolveFile(link); assert.equal(resolved.children!.length, 4); @@ -327,11 +327,11 @@ suite('Disk File Service', () => { test('resolveFile - file symbolic link', async () => { if (isWindows) { - return; // only for unix systems + return; // not happy } const link = URI.file(join(testDir, 'lorem.txt-linked')); - await promisify(exec)(`ln -s lorem.txt ${basename(link.fsPath)}`, { cwd: testDir }); + await symlink(join(testDir, 'lorem.txt'), link.fsPath); const resolved = await service.resolveFile(link); assert.equal(resolved.isDirectory, false); @@ -340,10 +340,11 @@ suite('Disk File Service', () => { test('resolveFile - invalid symbolic link does not break', async () => { if (isWindows) { - return; // only for unix systems + return; // not happy } - await promisify(exec)('ln -s foo bar', { cwd: testDir }); + const link = URI.file(join(testDir, 'foo')); + await symlink(link.fsPath, join(testDir, 'bar')); const resolved = await service.resolveFile(URI.file(testDir)); assert.equal(resolved.isDirectory, true); @@ -488,7 +489,7 @@ suite('Disk File Service', () => { assert.equal(existsSync(source.fsPath), false); assert.ok(event!); assert.equal(event!.resource.fsPath, source.fsPath); - assert.equal(event!.operation, FileOperation.MOVE); + assert.equal(event!.operation, FileOperation.COPY); assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath); const targetContents = readFileSync(target.fsPath); @@ -575,7 +576,7 @@ suite('Disk File Service', () => { assert.equal(existsSync(source.fsPath), false); assert.ok(event!); assert.equal(event!.resource.fsPath, source.fsPath); - assert.equal(event!.operation, FileOperation.MOVE); + assert.equal(event!.operation, FileOperation.COPY); assert.equal(event!.target!.resource.fsPath, renamed.resource.fsPath); const targetChildren = readdirSync(target.fsPath); diff --git a/src/vs/workbench/services/output/common/outputChannelModel.ts b/src/vs/workbench/services/output/common/outputChannelModel.ts index 8e8720cf656a..da5709a4568b 100644 --- a/src/vs/workbench/services/output/common/outputChannelModel.ts +++ b/src/vs/workbench/services/output/common/outputChannelModel.ts @@ -279,7 +279,7 @@ class FileOutputChannelModel extends AbstractFileOutputChannelModel implements I } } -class BufferredOutputChannel extends Disposable implements IOutputChannelModel { +export class BufferredOutputChannel extends Disposable implements IOutputChannelModel { readonly file: URI | null = null; scrollLock: boolean = false; diff --git a/src/vs/workbench/services/output/node/outputAppender.ts b/src/vs/workbench/services/output/node/outputAppender.ts index 5c7d2d6a5000..0bbb554645d9 100644 --- a/src/vs/workbench/services/output/node/outputAppender.ts +++ b/src/vs/workbench/services/output/node/outputAppender.ts @@ -9,7 +9,7 @@ export class OutputAppender { private appender: RotatingLogger; - constructor(name: string, file: string) { + constructor(name: string, readonly file: string) { this.appender = createRotatingLogger(name, file, 1024 * 1024 * 30, 1); this.appender.clearFormatters(); } diff --git a/src/vs/workbench/services/output/node/outputChannelModelService.ts b/src/vs/workbench/services/output/node/outputChannelModelService.ts index 9e98c2b4dc89..f3aab0c89ecc 100644 --- a/src/vs/workbench/services/output/node/outputChannelModelService.ts +++ b/src/vs/workbench/services/output/node/outputChannelModelService.ts @@ -6,21 +6,23 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import * as extfs from 'vs/base/node/extfs'; import { dirname, join } from 'vs/base/common/path'; +import * as resources from 'vs/base/common/resources'; import { ITextModel } from 'vs/editor/common/model'; import { URI } from 'vs/base/common/uri'; import { ThrottledDelayer } from 'vs/base/common/async'; import { IFileService } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { toDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; -import { IOutputChannelModel, AbstractFileOutputChannelModel, IOutputChannelModelService, AsbtractOutputChannelModelService } from 'vs/workbench/services/output/common/outputChannelModel'; +import { IOutputChannelModel, AbstractFileOutputChannelModel, IOutputChannelModelService, AsbtractOutputChannelModelService, BufferredOutputChannel } from 'vs/workbench/services/output/common/outputChannelModel'; import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { toLocalISOString } from 'vs/base/common/date'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Emitter, Event } from 'vs/base/common/event'; let watchingOutputDir = false; let callbacks: ((eventType: string, fileName?: string) => void)[] = []; @@ -55,15 +57,13 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement id: string, modelUri: URI, mimeType: string, - @IWindowService windowService: IWindowService, - @IEnvironmentService environmentService: IEnvironmentService, + file: URI, @IFileService fileService: IFileService, @IModelService modelService: IModelService, @IModeService modeService: IModeService, @ILogService logService: ILogService ) { - const outputDir = join(environmentService.logsPath, `output_${windowService.getCurrentWindowId()}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); - super(modelUri, mimeType, URI.file(join(outputDir, `${id}.log`)), fileService, modelService, modeService); + super(modelUri, mimeType, file, fileService, modelService, modeService); this.appendedMessage = ''; this.loadingFromFileInProgress = false; @@ -159,32 +159,93 @@ class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implement } } +class DelegatedOutputChannelModel extends Disposable implements IOutputChannelModel { + + private readonly _onDidAppendedContent: Emitter = this._register(new Emitter()); + readonly onDidAppendedContent: Event = this._onDidAppendedContent.event; + + private readonly _onDispose: Emitter = this._register(new Emitter()); + readonly onDispose: Event = this._onDispose.event; + + private readonly outputChannelModel: Promise; + + constructor( + id: string, + modelUri: URI, + mimeType: string, + outputDir: Promise, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ILogService private readonly logService: ILogService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + ) { + super(); + this.outputChannelModel = this.createOutputChannelModel(id, modelUri, mimeType, outputDir); + } + + private async createOutputChannelModel(id: string, modelUri: URI, mimeType: string, outputDirPromise: Promise): Promise { + let outputChannelModel: IOutputChannelModel; + try { + const outputDir = await outputDirPromise; + const file = resources.joinPath(outputDir, `${id}.log`); + outputChannelModel = this.instantiationService.createInstance(OutputChannelBackedByFile, id, modelUri, mimeType, file); + } catch (e) { + // Do not crash if spdlog rotating logger cannot be loaded (workaround for https://github.com/Microsoft/vscode/issues/47883) + this.logService.error(e); + /* __GDPR__ + "output.channel.creation.error" : {} + */ + this.telemetryService.publicLog('output.channel.creation.error'); + outputChannelModel = this.instantiationService.createInstance(BufferredOutputChannel, modelUri, mimeType); + } + this._register(outputChannelModel); + this._register(outputChannelModel.onDidAppendedContent(() => this._onDidAppendedContent.fire())); + this._register(outputChannelModel.onDispose(() => this._onDispose.fire())); + return outputChannelModel; + } + + append(output: string): void { + this.outputChannelModel.then(outputChannelModel => outputChannelModel.append(output)); + } + + update(): void { + this.outputChannelModel.then(outputChannelModel => outputChannelModel.update()); + } + + loadModel(): Promise { + return this.outputChannelModel.then(outputChannelModel => outputChannelModel.loadModel()); + } + + clear(till?: number): void { + this.outputChannelModel.then(outputChannelModel => outputChannelModel.clear(till)); + } + +} + export class OutputChannelModelService extends AsbtractOutputChannelModelService implements IOutputChannelModelService { _serviceBrand: any; constructor( @IInstantiationService instantiationService: IInstantiationService, - @ILogService private readonly logService: ILogService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IWindowService private readonly windowService: IWindowService, + @IFileService private readonly fileService: IFileService ) { super(instantiationService); } createOutputChannelModel(id: string, modelUri: URI, mimeType: string, file?: URI): IOutputChannelModel { - if (!file) { - try { - return this.instantiationService.createInstance(OutputChannelBackedByFile, id, modelUri, mimeType); - } catch (e) { - // Do not crash if spdlog rotating logger cannot be loaded (workaround for https://github.com/Microsoft/vscode/issues/47883) - this.logService.error(e); - /* __GDPR__ - "output.channel.creation.error" : {} - */ - this.telemetryService.publicLog('output.channel.creation.error'); - } + return file ? super.createOutputChannelModel(id, modelUri, mimeType, file) : + this.instantiationService.createInstance(DelegatedOutputChannelModel, id, modelUri, mimeType, this.outputDir); + } + + private _outputDir: Promise | null; + private get outputDir(): Promise { + if (!this._outputDir) { + const outputDir = URI.file(join(this.environmentService.logsPath, `output_${this.windowService.getCurrentWindowId()}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`)); + this._outputDir = this.fileService.createFolder(outputDir).then(() => outputDir); } - return super.createOutputChannelModel(id, modelUri, mimeType, file); + return this._outputDir; } } diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts index b5a99399b176..d2080bd57b24 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts @@ -90,7 +90,7 @@ suite('MainThreadDocumentsAndEditors', () => { test('Model#add', () => { deltas.length = 0; - modelService.createModel('farboo', null, null); + modelService.createModel('farboo', null); assert.equal(deltas.length, 1); const [delta] = deltas; @@ -105,7 +105,7 @@ suite('MainThreadDocumentsAndEditors', () => { test('ignore huge model', function () { this.timeout(1000 * 60); // increase timeout for this one test - const model = modelService.createModel(hugeModelString, null, null); + const model = modelService.createModel(hugeModelString, null); assert.ok(model.isTooLargeForSyncing()); assert.equal(deltas.length, 1); @@ -120,7 +120,7 @@ suite('MainThreadDocumentsAndEditors', () => { test('ignore simple widget model', function () { this.timeout(1000 * 60); // increase timeout for this one test - const model = modelService.createModel('test', null, null, true); + const model = modelService.createModel('test', null, undefined, true); assert.ok(model.isForSimpleWidget); assert.equal(deltas.length, 1); @@ -135,7 +135,7 @@ suite('MainThreadDocumentsAndEditors', () => { test('ignore huge model from editor', function () { this.timeout(1000 * 60); // increase timeout for this one test - const model = modelService.createModel(hugeModelString, null, null); + const model = modelService.createModel(hugeModelString, null); const editor = myCreateTestCodeEditor(model); assert.equal(deltas.length, 1); @@ -161,7 +161,7 @@ suite('MainThreadDocumentsAndEditors', () => { test('editor with model', () => { deltas.length = 0; - const model = modelService.createModel('farboo', null, null); + const model = modelService.createModel('farboo', null); const editor = myCreateTestCodeEditor(model); assert.equal(deltas.length, 2); @@ -182,8 +182,8 @@ suite('MainThreadDocumentsAndEditors', () => { }); test('editor with dispos-ed/-ing model', () => { - modelService.createModel('foobar', null, null); - const model = modelService.createModel('farboo', null, null); + modelService.createModel('foobar', null); + const model = modelService.createModel('farboo', null); const editor = myCreateTestCodeEditor(model); // ignore things until now diff --git a/yarn.lock b/yarn.lock index 8e5e9750c496..7c6865028835 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8419,10 +8419,10 @@ sparkles@^1.0.0: resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" integrity sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM= -spdlog@0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.7.2.tgz#9298753d7694b9ee9bbfd7e01ea1e4c6ace1e64d" - integrity sha512-rHfWCaWMD4NindDnql6rc6kn7Bs8JR92jhiUpCl3D6v+jYcQ6GozMLig0RliOOR8st5mU+IHLZnr15fBys5x/Q== +spdlog@0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.8.1.tgz#dfb3f3422ab3efe32be79e4769b95440ed72699f" + integrity sha512-W0s8IOXpn86md+8PJ4mJeB/22thykzH5YaNc3Rgnql4x4/zFIhvNiEx6/a1arnqvmJF0HtRO0Ehlswg0WcwTLQ== dependencies: bindings "^1.3.0" mkdirp "^0.5.1"