diff --git a/.eslintrc.json b/.eslintrc.json index 59775f27cb..5511bd261c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -196,6 +196,7 @@ "azdata", "**/{vs,sql}/base/common/**", "**/{vs,sql}/base/test/common/**", + "**/{vs,sql}/base/parts/*/common/**", "**/{vs,sql}/platform/*/common/**", "**/{vs,sql}/platform/*/test/common/**" ] diff --git a/scripts/code-web.js b/scripts/code-web.js index 26fb980e3d..4556e7d7e6 100755 --- a/scripts/code-web.js +++ b/scripts/code-web.js @@ -66,11 +66,11 @@ const server = http.createServer((req, res) => { // manifest res.writeHead(200, { 'Content-Type': 'application/json' }); return res.end(JSON.stringify({ - "name": "Code Web - OSS", - "short_name": "Code Web - OSS", - "start_url": "/", - "lang": "en-US", - "display": "standalone" + 'name': 'Code Web - OSS', + 'short_name': 'Code Web - OSS', + 'start_url': '/', + 'lang': 'en-US', + 'display': 'standalone' })); } if (/^\/static\//.test(pathname)) { diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index f40cc44a96..bb05408aed 100755 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -15,14 +15,16 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( echo Running integration tests out of sources. ) else ( :: Run from a built: need to compile all test extensions - call yarn gulp compile-extension:vscode-api-tests - call yarn gulp compile-extension:vscode-colorize-tests - call yarn gulp compile-extension:markdown-language-features - call yarn gulp compile-extension:emmet - call yarn gulp compile-extension:css-language-features-server - call yarn gulp compile-extension:html-language-features-server - call yarn gulp compile-extension:json-language-features-server - call yarn gulp compile-extension:git + :: because we run extension tests from their source folders + :: and the build bundles extensions into .build webpacked + call yarn gulp compile-extension:vscode-api-tests^ + compile-extension:vscode-colorize-tests^ + compile-extension:markdown-language-features^ + compile-extension:emmet^ + compile-extension:css-language-features-server^ + compile-extension:html-language-features-server^ + compile-extension:json-language-features-server^ + compile-extension:git :: Configuration for more verbose output set VSCODE_CLI=1 diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index a90a4870e4..065bccbb98 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -21,14 +21,16 @@ then echo "Running integration tests out of sources." else # Run from a built: need to compile all test extensions - yarn gulp compile-extension:vscode-api-tests - yarn gulp compile-extension:vscode-colorize-tests - yarn gulp compile-extension:markdown-language-features - yarn gulp compile-extension:emmet - yarn gulp compile-extension:css-language-features-server - yarn gulp compile-extension:html-language-features-server - yarn gulp compile-extension:json-language-features-server - yarn gulp compile-extension:git + # because we run extension tests from their source folders + # and the build bundles extensions into .build webpacked + yarn gulp compile-extension:vscode-api-tests \ + compile-extension:vscode-colorize-tests \ + compile-extension:markdown-language-features \ + compile-extension:emmet \ + compile-extension:css-language-features-server \ + compile-extension:html-language-features-server \ + compile-extension:json-language-features-server \ + compile-extension:git # Configuration for more verbose output export VSCODE_CLI=1 diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index 03fa06167d..6ec71738df 100644 --- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -5,7 +5,7 @@ @font-face { font-family: "codicon"; - src: url("./codicon.ttf?6caeeccc06315e827f3bff83885456fb") format("truetype"); + src: url("./codicon.ttf?d0510f6ecacbb2788db2b3162273a3d8") format("truetype"); } .codicon[class*='codicon-'] { diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf index a62bc6bd74..f712f5cd05 100644 Binary files a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf and b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf differ diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 6aca3015e4..040541fcce 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -779,3 +779,107 @@ export async function retry(task: ITask>, delay: number, retries: throw lastError; } + +//#region Task Sequentializer + +interface IPendingTask { + taskId: number; + cancel: () => void; + promise: Promise; +} + +interface ISequentialTask { + promise: Promise; + promiseResolve: () => void; + promiseReject: (error: Error) => void; + run: () => Promise; +} + +export interface ITaskSequentializerWithPendingTask { + readonly pending: Promise; +} + +export class TaskSequentializer { + private _pending?: IPendingTask; + private _next?: ISequentialTask; + + hasPending(taskId?: number): this is ITaskSequentializerWithPendingTask { + if (!this._pending) { + return false; + } + + if (typeof taskId === 'number') { + return this._pending.taskId === taskId; + } + + return !!this._pending; + } + + get pending(): Promise | undefined { + return this._pending ? this._pending.promise : undefined; + } + + cancelPending(): void { + this._pending?.cancel(); + } + + setPending(taskId: number, promise: Promise, onCancel?: () => void, ): Promise { + this._pending = { taskId: taskId, cancel: () => onCancel?.(), promise }; + + promise.then(() => this.donePending(taskId), () => this.donePending(taskId)); + + return promise; + } + + private donePending(taskId: number): void { + if (this._pending && taskId === this._pending.taskId) { + + // only set pending to done if the promise finished that is associated with that taskId + this._pending = undefined; + + // schedule the next task now that we are free if we have any + this.triggerNext(); + } + } + + private triggerNext(): void { + if (this._next) { + const next = this._next; + this._next = undefined; + + // Run next task and complete on the associated promise + next.run().then(next.promiseResolve, next.promiseReject); + } + } + + setNext(run: () => Promise): Promise { + + // this is our first next task, so we create associated promise with it + // so that we can return a promise that completes when the task has + // completed. + if (!this._next) { + let promiseResolve: () => void; + let promiseReject: (error: Error) => void; + const promise = new Promise((resolve, reject) => { + promiseResolve = resolve; + promiseReject = reject; + }); + + this._next = { + run, + promise, + promiseResolve: promiseResolve!, + promiseReject: promiseReject! + }; + } + + // we have a previous next task, just overwrite it + else { + this._next.run = run; + } + + return this._next.promise; + } +} + +//#endregion diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index 0b049d6559..7e0e1c2494 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -557,4 +557,93 @@ suite('Async', () => { assert.equal(error, error); } }); + + test('TaskSequentializer - pending basics', async function () { + const sequentializer = new async.TaskSequentializer(); + + assert.ok(!sequentializer.hasPending()); + assert.ok(!sequentializer.hasPending(2323)); + assert.ok(!sequentializer.pending); + + // pending removes itself after done + await sequentializer.setPending(1, Promise.resolve()); + assert.ok(!sequentializer.hasPending()); + assert.ok(!sequentializer.hasPending(1)); + assert.ok(!sequentializer.pending); + + // pending removes itself after done (use async.timeout) + sequentializer.setPending(2, async.timeout(1)); + assert.ok(sequentializer.hasPending()); + assert.ok(sequentializer.hasPending(2)); + assert.ok(!sequentializer.hasPending(1)); + assert.ok(sequentializer.pending); + + await async.timeout(2); + assert.ok(!sequentializer.hasPending()); + assert.ok(!sequentializer.hasPending(2)); + assert.ok(!sequentializer.pending); + }); + + test('TaskSequentializer - pending and next (finishes instantly)', async function () { + const sequentializer = new async.TaskSequentializer(); + + let pendingDone = false; + sequentializer.setPending(1, async.timeout(1).then(() => { pendingDone = true; return; })); + + // next finishes instantly + let nextDone = false; + const res = sequentializer.setNext(() => Promise.resolve(null).then(() => { nextDone = true; return; })); + + await res; + assert.ok(pendingDone); + assert.ok(nextDone); + }); + + test('TaskSequentializer - pending and next (finishes after timeout)', async function () { + const sequentializer = new async.TaskSequentializer(); + + let pendingDone = false; + sequentializer.setPending(1, async.timeout(1).then(() => { pendingDone = true; return; })); + + // next finishes after async.timeout + let nextDone = false; + const res = sequentializer.setNext(() => async.timeout(1).then(() => { nextDone = true; return; })); + + await res; + assert.ok(pendingDone); + assert.ok(nextDone); + }); + + test('TaskSequentializer - pending and multiple next (last one wins)', async function () { + const sequentializer = new async.TaskSequentializer(); + + let pendingDone = false; + sequentializer.setPending(1, async.timeout(1).then(() => { pendingDone = true; return; })); + + // next finishes after async.timeout + let firstDone = false; + let firstRes = sequentializer.setNext(() => async.timeout(2).then(() => { firstDone = true; return; })); + + let secondDone = false; + let secondRes = sequentializer.setNext(() => async.timeout(3).then(() => { secondDone = true; return; })); + + let thirdDone = false; + let thirdRes = sequentializer.setNext(() => async.timeout(4).then(() => { thirdDone = true; return; })); + + await Promise.all([firstRes, secondRes, thirdRes]); + assert.ok(pendingDone); + assert.ok(!firstDone); + assert.ok(!secondDone); + assert.ok(thirdDone); + }); + + test('TaskSequentializer - cancel pending', async function () { + const sequentializer = new async.TaskSequentializer(); + + let pendingCancelled = false; + sequentializer.setPending(1, async.timeout(1), () => pendingCancelled = true); + sequentializer.cancelPending(); + + assert.ok(pendingCancelled); + }); }); diff --git a/src/vs/editor/contrib/suggest/test/wordDistance.test.ts b/src/vs/editor/contrib/suggest/test/wordDistance.test.ts index abccf03aff..4d85e1d50d 100644 --- a/src/vs/editor/contrib/suggest/test/wordDistance.test.ts +++ b/src/vs/editor/contrib/suggest/test/wordDistance.test.ts @@ -105,8 +105,6 @@ suite('suggest, word distance', function () { } test('Suggest locality bonus can boost current word #90515', function () { - this.skip(); - const pos = { lineNumber: 2, column: 2 }; const d1 = distance.distance(pos, createSuggestItem('a', 1, pos).completion); const d2 = distance.distance(pos, createSuggestItem('aa', 1, pos).completion); diff --git a/src/vs/editor/contrib/suggest/wordDistance.ts b/src/vs/editor/contrib/suggest/wordDistance.ts index 3ec08e1172..b6e503b80a 100644 --- a/src/vs/editor/contrib/suggest/wordDistance.ts +++ b/src/vs/editor/contrib/suggest/wordDistance.ts @@ -35,14 +35,23 @@ export abstract class WordDistance { return WordDistance.None; } - const ranges = await new BracketSelectionRangeProvider().provideSelectionRanges(model, [position]); - if (!ranges || ranges.length === 0 || ranges[0].length === 0) { + const [ranges] = await new BracketSelectionRangeProvider().provideSelectionRanges(model, [position]); + if (ranges.length === 0) { return WordDistance.None; } - const wordRanges = await service.computeWordRanges(model.uri, ranges[0][0].range); + + const wordRanges = await service.computeWordRanges(model.uri, ranges[0].range); + if (!wordRanges) { + return WordDistance.None; + } + + // remove current word + const wordUntilPos = model.getWordUntilPosition(position); + delete wordRanges[wordUntilPos.word]; + return new class extends WordDistance { distance(anchor: IPosition, suggestion: CompletionItem) { - if (!wordRanges || !position.equals(editor.getPosition())) { + if (!position.equals(editor.getPosition())) { return 0; } if (suggestion.kind === CompletionItemKind.Keyword) { @@ -56,7 +65,7 @@ export abstract class WordDistance { let idx = binarySearch(wordLines, Range.fromPositions(anchor), Range.compareRangesUsingStarts); let bestWordRange = idx >= 0 ? wordLines[idx] : wordLines[Math.max(0, ~idx - 1)]; let blockDistance = ranges.length; - for (const range of ranges[0]) { + for (const range of ranges) { if (!Range.containsRange(range.range, bestWordRange)) { break; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index ae4a9a426d..dc17c2cf27 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -140,10 +140,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return synchroniser.accept(content); } - async hasPreviouslySynced(): Promise { + private async hasPreviouslySynced(): Promise { await this.checkEnablement(); for (const synchroniser of this.synchronisers) { if (await synchroniser.hasPreviouslySynced()) { + return true; } } return false; @@ -153,6 +154,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ await this.checkEnablement(); for (const synchroniser of this.synchronisers) { if (await synchroniser.hasLocalData()) { + return true; } } return false; @@ -189,6 +191,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ await this.checkEnablement(); for (const synchroniser of this.synchronisers) { try { + synchroniser.resetLocal(); } catch (e) { this.logService.error(`${synchroniser.source}: ${toErrorMessage(e)}`); this.logService.error(e); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 55986bcf4a..edee6914b4 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1430,7 +1430,7 @@ declare module 'vscode' { label: string; /** - * A human-readable string which is rendered less prominent in the same line. + * A human-readable string which is rendered less prominent on the same line. */ description?: string; diff --git a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts index 855d16ad21..0a6f1ed29a 100644 --- a/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/browser/mainThreadSaveParticipant.ts @@ -392,9 +392,9 @@ export class SaveParticipant implements ISaveParticipant { this._saveParticipants.dispose(); } - async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason; }): Promise { + async participate(model: IResolvedTextFileEditorModel, context: { reason: SaveReason; }, token: CancellationToken): Promise { - const cts = new CancellationTokenSource(); + const cts = new CancellationTokenSource(token); return this._progressService.withProgress({ title: localize('saveParticipants', "Running Save Participants for '{0}'", this._labelService.getUriLabel(model.resource, { relative: true })), @@ -410,7 +410,7 @@ export class SaveParticipant implements ISaveParticipant { break; } try { - const promise = p.participate(model, env, progress, cts.token); + const promise = p.participate(model, context, progress, cts.token); await raceCancellation(promise, cts.token); } catch (err) { this._logService.warn(err); diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index bb667b3a2b..a7cd607142 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -13,6 +13,7 @@ import { ITerminalChildProcess, ITerminalDimensions, EXT_HOST_CREATION_DELAY } f import { timeout } from 'vs/base/common/async'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { TerminalDataBufferer } from 'vs/workbench/contrib/terminal/common/terminalDataBuffering'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape { @@ -290,6 +291,7 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ protected _activeTerminal: ExtHostTerminal | undefined; protected _terminals: ExtHostTerminal[] = []; protected _terminalProcesses: { [id: number]: ITerminalChildProcess } = {}; + protected _terminalProcessDisposables: { [id: number]: IDisposable } = {}; protected _extensionTerminalAwaitingStart: { [id: number]: { initialDimensions: ITerminalDimensionsDto | undefined } | undefined } = {}; protected _getTerminalPromises: { [id: number]: Promise } = {}; @@ -332,7 +334,10 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal { const terminal = new ExtHostTerminal(this._proxy, options, options.name); const p = new ExtHostPseudoterminal(options.pty); - terminal.createExtensionTerminal().then(id => this._setupExtHostProcessListeners(id, p)); + terminal.createExtensionTerminal().then(id => { + const disposable = this._setupExtHostProcessListeners(id, p); + this._terminalProcessDisposables[id] = disposable; + }); this._terminals.push(terminal); return terminal; } @@ -343,7 +348,8 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ throw new Error(`Cannot resolve terminal with id ${id} for virtual process`); } const p = new ExtHostPseudoterminal(pty); - this._setupExtHostProcessListeners(id, p); + const disposable = this._setupExtHostProcessListeners(id, p); + this._terminalProcessDisposables[id] = disposable; } public async $acceptActiveTerminalChanged(id: number | null): Promise { @@ -474,16 +480,18 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ } - protected _setupExtHostProcessListeners(id: number, p: ITerminalChildProcess): void { - p.onProcessReady((e: { pid: number, cwd: string }) => this._proxy.$sendProcessReady(id, e.pid, e.cwd)); - p.onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title)); + protected _setupExtHostProcessListeners(id: number, p: ITerminalChildProcess): IDisposable { + const disposables = new DisposableStore(); + + disposables.add(p.onProcessReady((e: { pid: number, cwd: string }) => this._proxy.$sendProcessReady(id, e.pid, e.cwd))); + disposables.add(p.onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title))); // Buffer data events to reduce the amount of messages going to the renderer this._bufferer.startBuffering(id, p.onProcessData); - p.onProcessExit(exitCode => this._onProcessExit(id, exitCode)); + disposables.add(p.onProcessExit(exitCode => this._onProcessExit(id, exitCode))); if (p.onProcessOverrideDimensions) { - p.onProcessOverrideDimensions(e => this._proxy.$sendOverrideDimensions(id, e)); + disposables.add(p.onProcessOverrideDimensions(e => this._proxy.$sendOverrideDimensions(id, e))); } this._terminalProcesses[id] = p; @@ -492,6 +500,8 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ p.startSendingEvents(awaitingStart.initialDimensions); delete this._extensionTerminalAwaitingStart[id]; } + + return disposables; } public $acceptProcessInput(id: number, data: string): void { @@ -532,6 +542,13 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ delete this._terminalProcesses[id]; delete this._extensionTerminalAwaitingStart[id]; + // Clean up process disposables + const processDiposable = this._terminalProcessDisposables[id]; + if (processDiposable) { + processDiposable.dispose(); + delete this._terminalProcessDisposables[id]; + } + // Send exit event to main side this._proxy.$sendProcessExit(id, exitCode); } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index a6e02852cc..65408800c3 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -55,6 +55,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { EditorAutoSave } from 'vs/workbench/browser/parts/editor/editorAutoSave'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; // Register String Editor Registry.as(EditorExtensions.Editors).registerEditor( @@ -134,9 +135,21 @@ class UntitledTextEditorInputFactory implements IEditorInputFactory { resource = toLocalResource(resource, this.environmentService.configuration.remoteAuthority); // untitled with associated file path use the local schema } + // Mode: only remember mode if it is either specific (not text) + // or if the mode was explicitly set by the user. We want to preserve + // this information across restarts and not set the mode unless + // this is the case. + let modeId: string | undefined; + const modeIdCandidate = untitledTextEditorInput.getMode(); + if (modeIdCandidate !== PLAINTEXT_MODE_ID) { + modeId = modeIdCandidate; + } else if (untitledTextEditorInput.model.hasModeSetExplicitly) { + modeId = modeIdCandidate; + } + const serialized: ISerializedUntitledTextEditorInput = { resourceJSON: resource.toJSON(), - modeId: untitledTextEditorInput.getMode(), + modeId, encoding: untitledTextEditorInput.getEncoding() }; diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 22c25774c5..6c60adc719 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -214,6 +214,10 @@ export class TextResourceEditor extends AbstractTextResourceEditor { } private onDidEditorPaste(e: IPasteEvent, codeEditor: ICodeEditor): void { + if (this.input instanceof UntitledTextEditorInput && this.input.model.hasModeSetExplicitly) { + return; // do not override mode if it was set explicitly + } + if (e.range.startLineNumber !== 1 || e.range.startColumn !== 1) { return; // only when pasting into first line, first column (= empty document) } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index 0116009adc..bcd1001d51 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -222,4 +222,5 @@ export function registerNotificationCommands(center: INotificationsCenterControl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: SHOW_NOTIFICATIONS_CENTER, title: { value: localize('showNotifications', "Show Notifications"), original: 'Show Notifications' }, category }, when: NotificationsCenterVisibleContext.toNegated() }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: HIDE_NOTIFICATIONS_CENTER, title: { value: localize('hideNotifications', "Hide Notifications"), original: 'Hide Notifications' }, category }, when: NotificationsCenterVisibleContext }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLEAR_ALL_NOTIFICATIONS, title: { value: localize('clearAllNotifications', "Clear All Notifications"), original: 'Clear All Notifications' }, category } }); -} \ No newline at end of file + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: FOCUS_NOTIFICATION_TOAST, title: { value: localize('focusNotificationToasts', "Focus Notification Toast"), original: 'Focus Notification Toast' }, category }, when: NotificationsToastsVisibleContext }); +} diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index 69d86c06a9..18079b2cfc 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -93,20 +93,16 @@ class BrowserMain extends Disposable { // Layout const viewport = platform.isIOS && (window).visualViewport ? (window).visualViewport /** Visual viewport */ : window /** Layout viewport */; - this._register(addDisposableListener(viewport, EventType.RESIZE, () => { - workbench.layout(); - })); + this._register(addDisposableListener(viewport, EventType.RESIZE, () => workbench.layout())); // Prevent the back/forward gestures in macOS - this._register(addDisposableListener(this.domElement, EventType.WHEEL, (e) => { - e.preventDefault(); - }, { passive: false })); + this._register(addDisposableListener(this.domElement, EventType.WHEEL, e => e.preventDefault(), { passive: false })); // Prevent native context menus in web - this._register(addDisposableListener(this.domElement, EventType.CONTEXT_MENU, (e) => EventHelper.stop(e, true))); + this._register(addDisposableListener(this.domElement, EventType.CONTEXT_MENU, e => EventHelper.stop(e, true))); // Prevent default navigation on drop - this._register(addDisposableListener(this.domElement, EventType.DROP, (e) => EventHelper.stop(e, true))); + this._register(addDisposableListener(this.domElement, EventType.DROP, e => EventHelper.stop(e, true))); // Workbench Lifecycle this._register(workbench.onBeforeShutdown(event => { diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 2365a85370..3c75786311 100644 --- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -19,6 +19,7 @@ import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { ReplEvaluationResult } from 'vs/workbench/contrib/debug/common/replModel'; import { once } from 'vs/base/common/functional'; +import { ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; export const twistiePixels = 20; @@ -232,3 +233,11 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer; private needsRefresh = false; @@ -153,12 +154,6 @@ export class BreakpointsView extends ViewPane { this.onBreakpointsChange(); } })); - - this._register(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { - if (views.some(v => v.id === this.id)) { - this.list.updateOptions({ overrideStyles: { listBackground: this.getBackgroundColor() } }); - } - })); } public focus(): void { diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index c95b19ebad..292283414b 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -13,12 +13,12 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { renderViewTree, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IAction, Action } from 'vs/base/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { ILabelService } from 'vs/platform/label/common/label'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -74,7 +74,7 @@ export function getContextForContributedActions(element: CallStackItem | null): return ''; } -export class CallStackView extends ViewPane { +export class CallStackView extends BaseDebugViewPane { private pauseMessage!: HTMLSpanElement; private pauseMessageLabel!: HTMLSpanElement; private onCallStackChangeScheduler: RunOnceScheduler; @@ -298,12 +298,6 @@ export class CallStackView extends ViewPane { this.parentSessionToExpand.add(s.parentSession); } })); - - this._register(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { - if (views.some(v => v.id === this.id)) { - this.tree.updateOptions({ overrideStyles: { listBackground: this.getBackgroundColor() } }); - } - })); } layoutBody(height: number, width: number): void { diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index a74b669859..208ce32f86 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -165,7 +165,7 @@ export class DebugService implements IDebugService { this.debugUx.set(!!(this.state !== State.Inactive || this.configurationManager.selectedConfiguration.name) ? 'default' : 'simple'); })); this.toDispose.push(this.model.onDidChangeCallStack(() => { - const numberOfSessions = this.model.getSessions().length; + const numberOfSessions = this.model.getSessions().filter(s => !s.parentSession).length; if (this.activity) { this.activity.dispose(); } diff --git a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts index 3ae8d9dcb0..6a4818d8b7 100644 --- a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts +++ b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts @@ -72,7 +72,7 @@ export class DebugTaskRunner { await this.viewsService.openView(Constants.MARKERS_VIEW_ID); return Promise.resolve(TaskRunResult.Failure); } - if (onTaskErrors === 'cancel') { + if (onTaskErrors === 'abort') { return Promise.resolve(TaskRunResult.Failure); } @@ -85,7 +85,7 @@ export class DebugTaskRunner { ? nls.localize('preLaunchTaskExitCode', "The preLaunchTask '{0}' terminated with exit code {1}.", taskLabel, taskSummary.exitCode) : nls.localize('preLaunchTaskTerminated', "The preLaunchTask '{0}' terminated.", taskLabel); - const result = await this.dialogService.show(severity.Warning, message, [nls.localize('debugAnyway', "Debug Anyway"), nls.localize('showErrors', "Show Errors"), nls.localize('cancel', "Cancel")], { + const result = await this.dialogService.show(severity.Warning, message, [nls.localize('debugAnyway', "Debug Anyway"), nls.localize('showErrors', "Show Errors"), nls.localize('abort', "Abort")], { checkbox: { label: nls.localize('remember', "Remember my choice in user settings"), }, @@ -94,12 +94,12 @@ export class DebugTaskRunner { const debugAnyway = result.choice === 0; - const cancel = result.choice = 2; + const abort = result.choice === 2; if (result.checkboxChecked) { - this.configurationService.updateValue('debug.onTaskErrors', result.choice === 0 ? 'debugAnyway' : cancel ? 'cancel' : 'showErrors'); + this.configurationService.updateValue('debug.onTaskErrors', result.choice === 0 ? 'debugAnyway' : abort ? 'abort' : 'showErrors'); } - if (cancel) { + if (abort) { return Promise.resolve(TaskRunResult.Failure); } if (debugAnyway) { diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 3ecffec357..8462813bf0 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -7,12 +7,12 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { normalize, isAbsolute, posix } from 'vs/base/common/path'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { renderViewTree, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IDebugSession, IDebugService, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/contrib/debug/common/debug'; import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -402,7 +402,7 @@ function asTreeElement(item: BaseTreeItem, viewState?: IViewState): ITreeElement }; } -export class LoadedScriptsView extends ViewPane { +export class LoadedScriptsView extends BaseDebugViewPane { private treeContainer!: HTMLElement; private loadedScriptsItemType: IContextKey; @@ -573,12 +573,6 @@ export class LoadedScriptsView extends ViewPane { } })); - this._register(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { - if (views.some(v => v.id === this.id)) { - this.tree.updateOptions({ overrideStyles: { listBackground: this.getBackgroundColor() } }); - } - })); - // feature: expand all nodes when filtering (not when finding) let viewState: IViewState | undefined; this._register(this.tree.onDidChangeTypeFilterPattern(pattern => { diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 824568e77c..20a21600d2 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -5,7 +5,7 @@ /* Debug viewlet */ -.debug-viewlet { +.debug-pane { height: 100%; } @@ -13,27 +13,27 @@ height: 100%; } -.debug-viewlet .debug-start-view { +.debug-pane .debug-start-view { padding: 0 20px 0 20px; } -.debug-viewlet .debug-start-view .monaco-button, -.debug-viewlet .debug-start-view .section { +.debug-pane .debug-start-view .monaco-button, +.debug-pane .debug-start-view .section { margin-top: 20px; } -.debug-viewlet .debug-start-view .top-section { +.debug-pane .debug-start-view .top-section { margin-top: 10px; } -.debug-viewlet .debug-start-view .monaco-button { +.debug-pane .debug-start-view .monaco-button { max-width: 260px; margin-left: auto; margin-right: auto; display: block; } -.debug-viewlet .debug-start-view .click { +.debug-pane .debug-start-view .click { cursor: pointer; color: #007ACC; } @@ -87,7 +87,7 @@ /* Debug viewlet trees */ -.debug-viewlet .line-number { +.debug-pane .line-number { background: rgba(136, 136, 136, 0.3); border-radius: 2px; font-size: 0.9em; @@ -95,29 +95,29 @@ line-height: 20px; } -.debug-viewlet .monaco-list-row.selected .line-number, -.debug-viewlet .monaco-list-row.selected .thread > .state > .label, -.debug-viewlet .monaco-list-row.selected .session > .state > .label { +.debug-pane .monaco-list-row.selected .line-number, +.debug-pane .monaco-list-row.selected .thread > .state > .label, +.debug-pane .monaco-list-row.selected .session > .state > .label { background-color: #ffffff; color: #666; } -.debug-viewlet .monaco-list:focus .monaco-list-row.selected.focused .codicon { +.debug-pane .monaco-list:focus .monaco-list-row.selected.focused .codicon { color: inherit !important; } -.debug-viewlet .disabled { +.debug-pane .disabled { opacity: 0.65; } /* Call stack */ -.debug-viewlet .debug-call-stack-title { +.debug-pane .debug-call-stack-title { display: flex; width: 100%; } -.debug-viewlet .debug-call-stack-title > .pause-message { +.debug-pane .debug-call-stack-title > .pause-message { flex: 1; text-align: right; text-overflow: ellipsis; @@ -126,41 +126,41 @@ margin: 0px 10px; } -.debug-viewlet .debug-call-stack-title > .pause-message > .label { +.debug-pane .debug-call-stack-title > .pause-message > .label { border-radius: 3px; padding: 1px 2px; font-size: 9px; } -.debug-viewlet .debug-call-stack-title > .pause-message > .label.exception { +.debug-pane .debug-call-stack-title > .pause-message > .label.exception { background-color: #A31515; color: rgb(255, 255, 255); } -.vs-dark .debug-viewlet .debug-call-stack-title > .pause-message > .label.exception { +.vs-dark .debug-pane .debug-call-stack-title > .pause-message > .label.exception { background-color: #6C2022; color: inherit; } -.hc-black .debug-viewlet .debug-call-stack-title > .pause-message > .label.exception { +.hc-black .debug-pane .debug-call-stack-title > .pause-message > .label.exception { background-color: #6C2022; color: inherit; } -.debug-viewlet .debug-call-stack .thread, -.debug-viewlet .debug-call-stack .session { +.debug-pane .debug-call-stack .thread, +.debug-pane .debug-call-stack .session { display: flex; } -.debug-viewlet .debug-call-stack .thread > .name, -.debug-viewlet .debug-call-stack .session > .name { +.debug-pane .debug-call-stack .thread > .name, +.debug-pane .debug-call-stack .session > .name { flex: 1; overflow: hidden; text-overflow: ellipsis; } -.debug-viewlet .debug-call-stack .thread > .state, -.debug-viewlet .debug-call-stack .session > .state { +.debug-pane .debug-call-stack .thread > .state, +.debug-pane .debug-call-stack .session > .state { text-align: right; overflow: hidden; text-overflow: ellipsis; @@ -168,116 +168,116 @@ text-transform: uppercase; } -.debug-viewlet .debug-call-stack .monaco-list-row:hover .state { +.debug-pane .debug-call-stack .monaco-list-row:hover .state { display: none; } -.debug-viewlet .debug-call-stack .monaco-list-row:hover .stack-frame.has-actions .file .line-number { +.debug-pane .debug-call-stack .monaco-list-row:hover .stack-frame.has-actions .file .line-number { display: none; } -.debug-viewlet .debug-call-stack .monaco-list-row .monaco-action-bar { +.debug-pane .debug-call-stack .monaco-list-row .monaco-action-bar { display: none; } -.debug-viewlet .debug-call-stack .monaco-list-row:hover .monaco-action-bar { +.debug-pane .debug-call-stack .monaco-list-row:hover .monaco-action-bar { display: initial; } -.monaco-workbench .debug-viewlet .debug-call-stack .monaco-action-bar .action-item > .action-label { +.monaco-workbench .debug-pane .debug-call-stack .monaco-action-bar .action-item > .action-label { width: 16px; height: 100%; margin-right: 8px; vertical-align: text-top; } -.debug-viewlet .debug-call-stack .thread > .state > .label, -.debug-viewlet .debug-call-stack .session > .state > .label { +.debug-pane .debug-call-stack .thread > .state > .label, +.debug-pane .debug-call-stack .session > .state > .label { background: rgba(136, 136, 136, 0.3); border-radius: 2px; font-size: 0.8em; padding: 0 3px; } -.debug-viewlet .debug-call-stack .stack-frame { +.debug-pane .debug-call-stack .stack-frame { overflow: hidden; text-overflow: ellipsis; padding-right: 0.8em; display: flex; } -.debug-viewlet .debug-call-stack .stack-frame.label { +.debug-pane .debug-call-stack .stack-frame.label { text-align: center; font-style: italic; } -.debug-viewlet .debug-call-stack .stack-frame .label { +.debug-pane .debug-call-stack .stack-frame .label { flex: 1; flex-shrink: 0; min-width: fit-content; min-width: -moz-fit-content; } -.debug-viewlet .debug-call-stack .stack-frame.subtle { +.debug-pane .debug-call-stack .stack-frame.subtle { font-style: italic; } -.debug-viewlet .debug-call-stack .stack-frame.label > .file { +.debug-pane .debug-call-stack .stack-frame.label > .file { display: none; } -.debug-viewlet .debug-call-stack .stack-frame > .file { +.debug-pane .debug-call-stack .stack-frame > .file { display: flex; overflow: hidden; flex-wrap: wrap; justify-content: flex-end; } -.debug-viewlet .debug-call-stack .stack-frame > .file > .line-number.unavailable { +.debug-pane .debug-call-stack .stack-frame > .file > .line-number.unavailable { display: none; } -.debug-viewlet .debug-call-stack .monaco-list-row:not(.selected) .stack-frame > .file { +.debug-pane .debug-call-stack .monaco-list-row:not(.selected) .stack-frame > .file { color: rgba(108, 108, 108, 0.8); } -.debug-viewlet .debug-call-stack .stack-frame > .file > .file-name { +.debug-pane .debug-call-stack .stack-frame > .file > .file-name { overflow: hidden; text-overflow: ellipsis; margin-right: 0.8em; } -.vs-dark .debug-viewlet .debug-call-stack .monaco-list-row:not(.selected) .stack-frame > .file { +.vs-dark .debug-pane .debug-call-stack .monaco-list-row:not(.selected) .stack-frame > .file { color: rgba(204, 204, 204, 0.6); } -.debug-viewlet .debug-call-stack .stack-frame > .file:not(:first-child) { +.debug-pane .debug-call-stack .stack-frame > .file:not(:first-child) { margin-left: 0.8em; } -.debug-viewlet .debug-call-stack .load-more { +.debug-pane .debug-call-stack .load-more { font-style: italic; text-align: center; } -.debug-viewlet .debug-call-stack .show-more { +.debug-pane .debug-call-stack .show-more { font-style: italic; opacity: 0.35; } -.debug-viewlet .debug-call-stack .error { +.debug-pane .debug-call-stack .error { font-style: italic; text-overflow: ellipsis; overflow: hidden; } -.debug-viewlet .debug-call-stack .monaco-list:focus .monaco-list-row.selected .codicon { +.debug-pane .debug-call-stack .monaco-list:focus .monaco-list-row.selected .codicon { color: inherit !important; } /* Variables & Expression view */ -.debug-viewlet .scope { +.debug-pane .scope { font-weight: bold; font-size: 11px; } @@ -295,7 +295,7 @@ 100% { background-color: rgba(86, 156, 214, .2) } } -.debug-viewlet .monaco-list-row .expression .value.changed { +.debug-pane .monaco-list-row .expression .value.changed { padding: 2px; margin: 4px; border-radius: 4px; @@ -305,67 +305,67 @@ animation-fill-mode: forwards; } -.debug-viewlet .monaco-inputbox { +.debug-pane .monaco-inputbox { width: 100%; line-height: normal; } -.debug-viewlet .inputBoxContainer { +.debug-pane .inputBoxContainer { box-sizing: border-box; flex-grow: 1; } -.debug-viewlet .debug-watch .monaco-inputbox { +.debug-pane .debug-watch .monaco-inputbox { font-family: var(--monaco-monospace-font); } -.debug-viewlet .monaco-inputbox > .wrapper { +.debug-pane .monaco-inputbox > .wrapper { height: 19px; } -.debug-viewlet .monaco-inputbox > .wrapper > .input { +.debug-pane .monaco-inputbox > .wrapper > .input { padding: 0px; color: initial; } -.debug-viewlet .watch-expression { +.debug-pane .watch-expression { display: flex; } -.debug-viewlet .watch-expression .expression { +.debug-pane .watch-expression .expression { flex : 1; } -.vs-dark .debug-viewlet .monaco-list-row .expression .value.changed { +.vs-dark .debug-pane .monaco-list-row .expression .value.changed { animation-name: debugViewletValueChanged; } /* Breakpoints */ -.debug-viewlet .monaco-list-row { +.debug-pane .monaco-list-row { line-height: 22px; } -.debug-viewlet .debug-breakpoints .monaco-list-row .breakpoint { +.debug-pane .debug-breakpoints .monaco-list-row .breakpoint { padding-left: 2px; } -.debug-viewlet .debug-breakpoints .breakpoint.exception { +.debug-pane .debug-breakpoints .breakpoint.exception { padding-left: 20px; } -.debug-viewlet .debug-breakpoints .breakpoint { +.debug-pane .debug-breakpoints .breakpoint { display: flex; padding-right: 0.8em; flex: 1; align-items: center; } -.debug-viewlet .debug-breakpoints .breakpoint input { +.debug-pane .debug-breakpoints .breakpoint input { flex-shrink: 0; } -.debug-viewlet .debug-breakpoints .breakpoint > .codicon { +.debug-pane .debug-breakpoints .breakpoint > .codicon { width: 19px; height: 19px; min-width: 19px; @@ -374,7 +374,7 @@ justify-content: center; } -.debug-viewlet .debug-breakpoints .breakpoint > .file-path { +.debug-pane .debug-breakpoints .breakpoint > .file-path { opacity: 0.7; font-size: 0.9em; margin-left: 0.8em; @@ -383,7 +383,7 @@ overflow: hidden; } -.debug-viewlet .debug-breakpoints .breakpoint .name { +.debug-pane .debug-breakpoints .breakpoint .name { overflow: hidden; text-overflow: ellipsis } diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 0051ac6b6b..9ad97aaccd 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -12,12 +12,12 @@ import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IViewMod import { Variable, Scope } from 'vs/workbench/contrib/debug/common/debugModel'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IAction, Action } from 'vs/base/common/actions'; import { CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeMouseEvent, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; @@ -39,7 +39,7 @@ let forgetScopes = true; export const variableSetEmitter = new Emitter(); -export class VariablesView extends ViewPane { +export class VariablesView extends BaseDebugViewPane { private onFocusStackFrameScheduler: RunOnceScheduler; private needsRefresh = false; @@ -143,11 +143,6 @@ export class VariablesView extends ViewPane { this.tree.rerender(e); } })); - this._register(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { - if (views.some(v => v.id === this.id)) { - this.tree.updateOptions({ overrideStyles: { listBackground: this.getBackgroundColor() } }); - } - })); } getActions(): IAction[] { diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index a6920ca611..10faaaed9f 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -16,9 +16,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData, BaseDebugViewPane } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -38,7 +38,7 @@ const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; let ignoreVariableSetEmitter = false; let useCachedEvaluation = false; -export class WatchExpressionsView extends ViewPane { +export class WatchExpressionsView extends BaseDebugViewPane { private onWatchExpressionsUpdatedScheduler: RunOnceScheduler; private needsRefresh = false; @@ -138,11 +138,6 @@ export class WatchExpressionsView extends ViewPane { this.tree.rerender(e); } })); - this._register(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { - if (views.some(v => v.id === this.id)) { - this.tree.updateOptions({ overrideStyles: { listBackground: this.getBackgroundColor() } }); - } - })); } layoutBody(height: number, width: number): void { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 108fc68c2d..b5173fa979 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -467,7 +467,7 @@ export interface IDebugConfiguration { closeOnEnd: boolean; }; focusWindowOnBreak: boolean; - onTaskErrors: 'debugAnyway' | 'showErrors' | 'prompt' | 'cancel'; + onTaskErrors: 'debugAnyway' | 'showErrors' | 'prompt' | 'abort'; showBreakpointsInOverviewRuler: boolean; showInlineBreakpointCandidates: boolean; } diff --git a/src/vs/workbench/contrib/debug/common/debugSource.ts b/src/vs/workbench/contrib/debug/common/debugSource.ts index 1629ad986d..7daccef440 100644 --- a/src/vs/workbench/contrib/debug/common/debugSource.ts +++ b/src/vs/workbench/contrib/debug/common/debugSource.ts @@ -73,7 +73,7 @@ export class Source { openInEditor(editorService: IEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise { return !this.available ? Promise.resolve(undefined) : editorService.openEditor({ - resource: this.uri.with({ query: null }), + resource: this.uri, description: this.origin, options: { preserveFocus, diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts index a23e57c6c0..885140bfc3 100644 --- a/src/vs/workbench/contrib/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -94,7 +94,8 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments } let quote: (s: string) => string; - let command = ''; + // begin command with a space to avoid polluting shell history + let command = ' '; switch (shellType) { diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index c4bb556959..708a8cd21b 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -108,6 +108,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor order: 0, when: OpenEditorsVisibleContext, canToggleVisibility: true, + canMoveView: true, focusCommand: { id: 'workbench.files.action.focusOpenEditorsView', keybindings: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_E) } diff --git a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index fc8387e3b9..2ed39d7ff8 100644 --- a/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -19,7 +19,6 @@ } .explorer-viewlet .explorer-item, -.explorer-viewlet .open-editor, .explorer-viewlet .editor-group { height: 22px; line-height: 22px; @@ -31,7 +30,6 @@ } .explorer-viewlet .explorer-item > a, -.explorer-viewlet .open-editor > a, .explorer-viewlet .editor-group { text-overflow: ellipsis; overflow: hidden; @@ -50,16 +48,6 @@ flex: 0; /* do not steal space when label is hidden because we are in edit mode */ } -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row { - padding-left: 22px; - display: flex; -} - -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar { - visibility: hidden; - display: flex; - align-items: center; -} .explorer-viewlet .pane-header .count { min-width: fit-content; @@ -72,42 +60,6 @@ display: none; } -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row:hover > .monaco-action-bar, -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.focused > .monaco-action-bar, -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row.dirty > .monaco-action-bar { - visibility: visible; -} - -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-label { - display: block; -} - -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar .codicon { - color: inherit; -} - -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar .codicon-close { - width: 8px; - height: 22px; - display: flex; - align-items: center; - justify-content: center; -} - -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-close-all-files, -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .monaco-action-bar .save-all { - width: 23px; - height: 22px; -} - -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .open-editor { - flex: 1; -} - -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row > .editor-group { - flex: 1; -} - .explorer-viewlet .monaco-count-badge { padding: 1px 6px 2px; margin-left: 6px; @@ -155,24 +107,7 @@ height: 20px; } -.explorer-viewlet .explorer-open-editors .monaco-list .monaco-list-row .editor-group { - font-size: 11px; - font-weight: bold; - text-transform: uppercase; - cursor: default; -} - -/* Bold font style does not go well with CJK fonts */ -.explorer-viewlet:lang(zh-Hans) .explorer-open-editors .monaco-list .monaco-list-row .editor-group, -.explorer-viewlet:lang(zh-Hant) .explorer-open-editors .monaco-list .monaco-list-row .editor-group, -.explorer-viewlet:lang(ja) .explorer-open-editors .monaco-list .monaco-list-row .editor-group, -.explorer-viewlet:lang(ko) .explorer-open-editors .monaco-list .monaco-list-row .editor-group { - font-weight: normal; -} - /* High Contrast Theming */ -.hc-black .monaco-workbench .explorer-viewlet .explorer-item, -.hc-black .monaco-workbench .explorer-viewlet .open-editor, -.hc-black .monaco-workbench .explorer-viewlet .editor-group { +.hc-black .monaco-workbench .explorer-viewlet .explorer-item { line-height: 20px; } diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index 775657afcf..d70a244e3a 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -50,7 +50,7 @@ export class EmptyView extends ViewPane { @IContextKeyService contextKeyService: IContextKeyService, @IOpenerService openerService: IOpenerService ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: No Folder Opened") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); this._register(this.contextService.onDidChangeWorkbenchState(() => this.setLabels())); this._register(this.labelService.onDidChangeFormatters(() => this.setLabels())); } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 4b2c3dbfc8..709d14f47d 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -172,7 +172,7 @@ export class ExplorerView extends ViewPane { @IFileService private readonly fileService: IFileService, @IOpenerService openerService: IOpenerService, ) { - super({ ...(options as IViewPaneOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); + super({ ...(options as IViewPaneOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Explorer Section: {0}", labelService.getWorkspaceLabel(contextService.getWorkspace())) }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService); this.resourceContext = instantiationService.createInstance(ResourceContextKey); this._register(this.resourceContext); @@ -229,6 +229,7 @@ export class ExplorerView extends ViewPane { const title = workspace.folders.map(folder => folder.name).join(); titleElement.textContent = this.name; titleElement.title = title; + titleElement.setAttribute('aria-label', nls.localize('explorerSection', "Explorer Section: {0}", this.name)); }; this._register(this.contextService.onDidChangeWorkspaceName(setHeader)); diff --git a/src/vs/workbench/contrib/files/browser/views/media/openeditors.css b/src/vs/workbench/contrib/files/browser/views/media/openeditors.css new file mode 100644 index 0000000000..63cffe631e --- /dev/null +++ b/src/vs/workbench/contrib/files/browser/views/media/openeditors.css @@ -0,0 +1,83 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.open-editors .monaco-list .monaco-list-row:hover > .monaco-action-bar, +.open-editors .monaco-list .monaco-list-row.focused > .monaco-action-bar, +.open-editors .monaco-list .monaco-list-row.dirty > .monaco-action-bar { + visibility: visible; +} + +.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-label { + display: block; +} + +.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .codicon { + color: inherit; +} + +.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .codicon-close { + width: 8px; + height: 22px; + display: flex; + align-items: center; + justify-content: center; +} + +.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-close-all-files, +.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .save-all { + width: 23px; + height: 22px; +} + +.open-editors .monaco-list .monaco-list-row > .open-editor { + flex: 1; +} + +.open-editors .monaco-list .monaco-list-row > .editor-group { + flex: 1; +} + +.open-editors .monaco-list .monaco-list-row { + padding-left: 22px; + display: flex; +} + +.open-editors .monaco-list .monaco-list-row > .monaco-action-bar { + visibility: hidden; + display: flex; + align-items: center; +} + +.open-editors .monaco-list .monaco-list-row .editor-group { + font-size: 11px; + font-weight: bold; + text-transform: uppercase; + cursor: default; +} + +/* Bold font style does not go well with CJK fonts */ +.composite:lang(zh-Hans) .open-editors .monaco-list .monaco-list-row .editor-group, +.composite:lang(zh-Hant) .open-editors .monaco-list .monaco-list-row .editor-group, +.composite:lang(ja) .open-editors .monaco-list .monaco-list-row .editor-group, +.composite:lang(ko) .open-editors .monaco-list .monaco-list-row .editor-group { + font-weight: normal; +} + +.open-editors .open-editor, +.open-editors .editor-group { + height: 22px; + line-height: 22px; +} + +.open-editors .open-editor > a, +.open-editors .editor-group { + text-overflow: ellipsis; + overflow: hidden; +} + +.hc-black .monaco-workbench .open-editors .open-editor, +.hc-black .monaco-workbench .open-editors .editor-group { + line-height: 20px; +} diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 17b125395e..5ea64f16c0 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/openeditors'; import * as nls from 'vs/nls'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IAction, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; @@ -42,7 +43,6 @@ import { URI } from 'vs/base/common/uri'; import { withUndefinedAsNull } from 'vs/base/common/types'; import { isWeb } from 'vs/base/common/platform'; import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -206,7 +206,7 @@ export class OpenEditorsView extends ViewPane { renderBody(container: HTMLElement): void { super.renderBody(container); - dom.addClass(container, 'explorer-open-editors'); + dom.addClass(container, 'open-editors'); dom.addClass(container, 'show-file-icons'); const delegate = new OpenEditorsDelegate(); @@ -225,7 +225,7 @@ export class OpenEditorsView extends ViewPane { identityProvider: { getId: (element: OpenEditor | IEditorGroup) => element instanceof OpenEditor ? element.getId() : element.id.toString() }, dnd: new OpenEditorsDragAndDrop(this.instantiationService, this.editorGroupService), overrideStyles: { - listBackground: SIDE_BAR_BACKGROUND + listBackground: this.getBackgroundColor() } }); this._register(this.list); diff --git a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts index fc45f2312c..297a33b63c 100644 --- a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts @@ -7,8 +7,9 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { OpenLogsFolderAction } from 'vs/workbench/contrib/logs/electron-browser/logsActions'; +import { OpenLogsFolderAction, OpenExtensionLogsFolderAction } from 'vs/workbench/contrib/logs/electron-browser/logsActions'; const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); const devCategory = nls.localize('developer', "Developer"); workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); +workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.create(OpenExtensionLogsFolderAction, OpenExtensionLogsFolderAction.ID, OpenExtensionLogsFolderAction.LABEL), 'Developer: Open Extension Logs Folder', devCategory); diff --git a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts index b4c3dc821e..e40f5b3297 100644 --- a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts +++ b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts @@ -3,12 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { join } from 'vs/base/common/path'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; +import * as nls from 'vs/nls'; import { IElectronService } from 'vs/platform/electron/node/electron'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; +import { IFileService } from 'vs/platform/files/common/files'; export class OpenLogsFolderAction extends Action { @@ -26,3 +28,24 @@ export class OpenLogsFolderAction extends Action { return this.electronService.showItemInFolder(URI.file(join(this.environmentService.logsPath, 'main.log')).fsPath); } } + +export class OpenExtensionLogsFolderAction extends Action { + + static readonly ID = 'workbench.action.openExtensionLogsFolder'; + static readonly LABEL = nls.localize('openExtensionLogsFolder', "Open Extension Logs Folder"); + + constructor(id: string, label: string, + @IElectronEnvironmentService private readonly electronEnvironmentSerice: IElectronEnvironmentService, + @IFileService private readonly fileService: IFileService, + @IElectronService private readonly electronService: IElectronService + ) { + super(id, label); + } + + async run(): Promise { + const folderStat = await this.fileService.resolve(this.electronEnvironmentSerice.extHostLogsPath); + if (folderStat.children && folderStat.children[0]) { + return this.electronService.showItemInFolder(folderStat.children[0].resource.fsPath); + } + } +} diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index a64676466d..d8bf8ce87c 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -18,6 +18,8 @@ import { joinPath } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; import { TunnelFactoryContribution } from 'vs/workbench/contrib/remote/common/tunnelFactory'; import { ShowCandidateContribution } from 'vs/workbench/contrib/remote/common/showCandidate'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; export const VIEWLET_ID = 'workbench.view.remote'; @@ -90,3 +92,37 @@ workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContr workbenchContributionsRegistry.registerWorkbenchContribution(RemoteLogOutputChannels, LifecyclePhase.Restored); workbenchContributionsRegistry.registerWorkbenchContribution(TunnelFactoryContribution, LifecyclePhase.Ready); workbenchContributionsRegistry.registerWorkbenchContribution(ShowCandidateContribution, LifecyclePhase.Ready); + +const extensionKindSchema: IJSONSchema = { + type: 'string', + enum: [ + 'ui', + 'workspace' + ], + enumDescriptions: [ + localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."), + localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.") + ], +}; + +Registry.as(ConfigurationExtensions.Configuration) + .registerConfiguration({ + id: 'remote', + title: localize('remote', "Remote"), + type: 'object', + properties: { + 'remote.extensionKind': { + type: 'object', + markdownDescription: localize('remote.extensionKind', "Override the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions are run on the remote. By overriding an extension's default kind using this setting, you specify if that extension should be installed and enabled locally or remotely."), + patternProperties: { + '([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$': { + oneOf: [{ type: 'array', items: extensionKindSchema }, extensionKindSchema], + default: ['ui'], + }, + }, + default: { + 'pub.name': ['ui'] + } + }, + } + }); diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index eff41ff7f1..3e3e662fce 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -38,7 +38,6 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { RemoteConnectionState, Deprecated_RemoteAuthorityContext, RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; import { IDownloadService } from 'vs/platform/download/common/download'; import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderCommand, SaveLocalFileCommand } from 'vs/workbench/services/dialogs/browser/simpleFileDialog'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; const WINDOW_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu'; const CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close'; @@ -370,37 +369,12 @@ workbenchContributionsRegistry.registerWorkbenchContribution(RemoteWindowActiveI workbenchContributionsRegistry.registerWorkbenchContribution(RemoteTelemetryEnablementUpdater, LifecyclePhase.Ready); workbenchContributionsRegistry.registerWorkbenchContribution(RemoteEmptyWorkbenchPresentation, LifecyclePhase.Starting); -const extensionKindSchema: IJSONSchema = { - type: 'string', - enum: [ - 'ui', - 'workspace' - ], - enumDescriptions: [ - nls.localize('ui', "UI extension kind. In a remote window, such extensions are enabled only when available on the local machine."), - nls.localize('workspace', "Workspace extension kind. In a remote window, such extensions are enabled only when available on the remote.") - ], -}; - Registry.as(ConfigurationExtensions.Configuration) .registerConfiguration({ id: 'remote', title: nls.localize('remote', "Remote"), type: 'object', properties: { - 'remote.extensionKind': { - type: 'object', - markdownDescription: nls.localize('remote.extensionKind', "Override the kind of an extension. `ui` extensions are installed and run on the local machine while `workspace` extensions are run on the remote. By overriding an extension's default kind using this setting, you specify if that extension should be installed and enabled locally or remotely."), - patternProperties: { - '([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$': { - oneOf: [{ type: 'array', items: extensionKindSchema }, extensionKindSchema], - default: ['ui'], - }, - }, - default: { - 'pub.name': ['ui'] - } - }, 'remote.downloadExtensionsLocally': { type: 'boolean', markdownDescription: nls.localize('remote.downloadExtensionsLocally', "When enabled extensions are downloaded locally and installed on remote."), diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index dc87f1bf0a..fea5fd4ef8 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -26,7 +26,7 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE import * as SearchConstants from 'vs/workbench/contrib/search/common/constants'; import * as SearchEditorConstants from 'vs/workbench/contrib/searchEditor/browser/constants'; import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor'; -import { OpenResultsInEditorAction, OpenSearchEditorAction, ReRunSearchEditorSearchAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; +import { OpenResultsInEditorAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorContextLinesCommand, toggleSearchEditorRegexCommand, toggleSearchEditorWholeWordCommand } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions'; import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/searchEditor/browser/searchEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; @@ -164,10 +164,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const registry = Registry.as(ActionExtensions.WorkbenchActions); const category = localize('search', "Search Editor"); -registry.registerWorkbenchAction( - SyncActionDescriptor.create(ReRunSearchEditorSearchAction, ReRunSearchEditorSearchAction.ID, ReRunSearchEditorSearchAction.LABEL), - 'Search Editor: Rerun search', category, ContextKeyExpr.and(SearchEditorConstants.InSearchEditor)); - registry.registerWorkbenchAction( SyncActionDescriptor.create(OpenResultsInEditorAction, OpenResultsInEditorAction.ID, OpenResultsInEditorAction.LABEL, { mac: { primary: KeyMod.CtrlCmd | KeyCode.Enter } }, diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts index c54c5a4ab0..5aae652e00 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts @@ -112,25 +112,6 @@ export class OpenResultsInEditorAction extends Action { } } - -export class ReRunSearchEditorSearchAction extends Action { - - static readonly ID = 'searchEditor.rerunSerach'; - static readonly LABEL = localize('search.rerunSearch', "Rerun Search in Editor"); - - constructor(id: string, label: string, - @IEditorService private readonly editorService: IEditorService) { - super(id, label); - } - - async run() { - const input = this.editorService.activeEditor; - if (input instanceof SearchEditorInput) { - await (this.editorService.activeControl as SearchEditor).runSearch(false, true); - } - } -} - const openNewSearchEditor = async (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); diff --git a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts index dc92eefa47..4f53109ecd 100644 --- a/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts +++ b/src/vs/workbench/contrib/timeline/browser/timeline.contribution.ts @@ -26,6 +26,8 @@ export class TimelinePaneDescriptor implements IViewDescriptor { readonly collapsed = true; readonly canToggleVisibility = true; readonly hideByDefault = false; + readonly canMoveView = true; + focusCommand = { id: 'timeline.focus' }; } diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts index 908cfd9fdd..40356e87e1 100644 --- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts +++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts @@ -294,7 +294,10 @@ export class TimelinePane extends ViewPane { const renderer = this.instantiationService.createInstance(TimelineTreeRenderer); this._tree = >this.instantiationService.createInstance(WorkbenchObjectTree, 'TimelinePane', this._treeElement, new TimelineListVirtualDelegate(), [renderer], { identityProvider: new TimelineIdentityProvider(), - keyboardNavigationLabelProvider: new TimelineKeyboardNavigationLabelProvider() + keyboardNavigationLabelProvider: new TimelineKeyboardNavigationLabelProvider(), + overrideStyles: { + listBackground: this.getBackgroundColor() + } }); const customTreeNavigator = new TreeResourceNavigator(this._tree, { openOnFocus: false, openOnSelection: false }); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 65d6a642ec..80bb4817f9 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -147,13 +147,13 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } if (sessions.length === 0) { - this.setActiveAccount(undefined); + await this.setActiveAccount(undefined); return; } if (sessions.length === 1) { this.logAuthenticatedEvent(sessions[0]); - this.setActiveAccount(sessions[0]); + await this.setActiveAccount(sessions[0]); return; } @@ -167,7 +167,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo if (selectedAccount) { const selected = sessions.filter(account => selectedAccount.id === account.id)[0]; this.logAuthenticatedEvent(selected); - this.setActiveAccount(selected); + await this.setActiveAccount(selected); } } @@ -565,7 +565,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private async signIn(): Promise { try { - this.setActiveAccount(await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access'])); + await this.setActiveAccount(await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access'])); } catch (e) { this.notificationService.error(e); throw e; @@ -575,7 +575,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private async signOut(): Promise { if (this.activeAccount) { await this.authenticationService.logout(this.userDataSyncStore!.authenticationProviderId, this.activeAccount.id); - this.setActiveAccount(undefined); + await this.setActiveAccount(undefined); } } diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index 436c4d85a6..cc61064ca2 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -182,7 +182,8 @@ class DesktopMain extends Disposable { serviceCollection.set(IWorkbenchEnvironmentService, this.environmentService); serviceCollection.set(IElectronEnvironmentService, new ElectronEnvironmentService( this.configuration.windowId, - this.environmentService.sharedIPCHandle + this.environmentService.sharedIPCHandle, + this.environmentService )); // Product diff --git a/src/vs/workbench/services/electron/electron-browser/electronEnvironmentService.ts b/src/vs/workbench/services/electron/electron-browser/electronEnvironmentService.ts index 4da6ebecff..54c3e86a16 100644 --- a/src/vs/workbench/services/electron/electron-browser/electronEnvironmentService.ts +++ b/src/vs/workbench/services/electron/electron-browser/electronEnvironmentService.ts @@ -4,6 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; +import { memoize } from 'vs/base/common/decorators'; +import { join } from 'vs/base/common/path'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export const IElectronEnvironmentService = createDecorator('electronEnvironmentService'); @@ -14,6 +18,8 @@ export interface IElectronEnvironmentService { readonly windowId: number; readonly sharedIPCHandle: string; + + readonly extHostLogsPath: URI; } export class ElectronEnvironmentService implements IElectronEnvironmentService { @@ -22,6 +28,10 @@ export class ElectronEnvironmentService implements IElectronEnvironmentService { constructor( public readonly windowId: number, - public readonly sharedIPCHandle: string + public readonly sharedIPCHandle: string, + private readonly environmentService: IEnvironmentService ) { } + + @memoize + get extHostLogsPath(): URI { return URI.file(join(this.environmentService.logsPath, `exthost${this.windowId}`)); } } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index e8e23e4899..8e717fc3cd 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -9,9 +9,7 @@ import { CachedExtensionScanner } from 'vs/workbench/services/extensions/electro import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { AbstractExtensionService } from 'vs/workbench/services/extensions/common/abstractExtensionService'; import * as nls from 'vs/nls'; -import * as path from 'vs/base/common/path'; import { runWhenIdle } from 'vs/base/common/async'; -import { URI } from 'vs/base/common/uri'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -55,7 +53,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten private readonly _remoteExtensionsEnvironmentData: Map; - private readonly _extensionHostLogsLocation: URI; private readonly _extensionScanner: CachedExtensionScanner; private _deltaExtensionsQueue: DeltaExtensionsQueueItem[]; @@ -99,7 +96,6 @@ export class ExtensionService extends AbstractExtensionService implements IExten this._remoteExtensionsEnvironmentData = new Map(); - this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._electronEnvironmentService.windowId}`)); this._extensionScanner = instantiationService.createInstance(CachedExtensionScanner); this._deltaExtensionsQueue = []; @@ -368,7 +364,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten const result: ExtensionHostProcessManager[] = []; - const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._extensionHostLogsLocation); + const extHostProcessWorker = this._instantiationService.createInstance(ExtensionHostProcessWorker, autoStart, extensions, this._electronEnvironmentService.extHostLogsPath); const extHostProcessManager = this._instantiationService.createInstance(ExtensionHostProcessManager, true, extHostProcessWorker, null, initialActivationEvents); result.push(extHostProcessManager); diff --git a/src/vs/workbench/services/textfile/common/saveSequenzializer.ts b/src/vs/workbench/services/textfile/common/saveSequenzializer.ts deleted file mode 100644 index 20e294ceb6..0000000000 --- a/src/vs/workbench/services/textfile/common/saveSequenzializer.ts +++ /dev/null @@ -1,95 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -interface IPendingSave { - versionId: number; - promise: Promise; -} - -interface ISaveOperation { - promise: Promise; - promiseResolve: () => void; - promiseReject: (error: Error) => void; - run: () => Promise; -} - -export class SaveSequentializer { - private _pendingSave?: IPendingSave; - private _nextSave?: ISaveOperation; - - hasPendingSave(versionId?: number): boolean { - if (!this._pendingSave) { - return false; - } - - if (typeof versionId === 'number') { - return this._pendingSave.versionId === versionId; - } - - return !!this._pendingSave; - } - - get pendingSave(): Promise | undefined { - return this._pendingSave ? this._pendingSave.promise : undefined; - } - - setPending(versionId: number, promise: Promise): Promise { - this._pendingSave = { versionId, promise }; - - promise.then(() => this.donePending(versionId), () => this.donePending(versionId)); - - return promise; - } - - private donePending(versionId: number): void { - if (this._pendingSave && versionId === this._pendingSave.versionId) { - - // only set pending to done if the promise finished that is associated with that versionId - this._pendingSave = undefined; - - // schedule the next save now that we are free if we have any - this.triggerNextSave(); - } - } - - private triggerNextSave(): void { - if (this._nextSave) { - const saveOperation = this._nextSave; - this._nextSave = undefined; - - // Run next save and complete on the associated promise - saveOperation.run().then(saveOperation.promiseResolve, saveOperation.promiseReject); - } - } - - setNext(run: () => Promise): Promise { - - // this is our first next save, so we create associated promise with it - // so that we can return a promise that completes when the save operation - // has completed. - if (!this._nextSave) { - let promiseResolve: () => void; - let promiseReject: (error: Error) => void; - const promise = new Promise((resolve, reject) => { - promiseResolve = resolve; - promiseReject = reject; - }); - - this._nextSave = { - run, - promise, - promiseResolve: promiseResolve!, - promiseReject: promiseReject! - }; - } - - // we have a previous next save, just overwrite it - else { - this._nextSave.run = run; - } - - return this._nextSave.promise; - } -} diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 4e01878449..65ad8b6773 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -14,7 +14,7 @@ import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backu import { IFileService, FileOperationError, FileOperationResult, FileChangesEvent, FileChangeType, IFileStatWithMetadata, ETAG_DISABLED, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { timeout } from 'vs/base/common/async'; +import { timeout, TaskSequentializer } from 'vs/base/common/async'; import { ITextBufferFactory, ITextModel } from 'vs/editor/common/model'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ILogService } from 'vs/platform/log/common/log'; @@ -22,8 +22,8 @@ import { basename } from 'vs/base/common/path'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IWorkingCopyService, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { SaveSequentializer } from 'vs/workbench/services/textfile/common/saveSequenzializer'; import { ILabelService } from 'vs/platform/label/common/label'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; interface IBackupMetaData { mtime: number; @@ -78,7 +78,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private lastResolvedFileStat: IFileStatWithMetadata | undefined; - private readonly saveSequentializer = new SaveSequentializer(); + private readonly saveSequentializer = new TaskSequentializer(); private lastSaveAttemptTime = 0; private dirty = false; @@ -255,7 +255,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // It is very important to not reload the model when the model is dirty. // We also only want to reload the model from the disk if no save is pending // to avoid data loss. - if (this.dirty || this.saveSequentializer.hasPendingSave()) { + if (this.dirty || this.saveSequentializer.hasPending()) { this.logService.trace('[text file model] load() - exit - without loading because model is dirty or being saved', this.resource.toString()); return this; @@ -575,10 +575,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Scenario: user invoked the save action multiple times quickly for the same contents // while the save was not yet finished to disk // - if (this.saveSequentializer.hasPendingSave(versionId)) { + if (this.saveSequentializer.hasPending(versionId)) { this.logService.trace(`[text file model] doSave(${versionId}) - exit - found a pending save for versionId ${versionId}`, this.resource.toString()); - return this.saveSequentializer.pendingSave || Promise.resolve(); + return this.saveSequentializer.pending; } // Return early if not dirty (unless forced) @@ -598,11 +598,18 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Scenario B: save is very slow (e.g. network share) and the user manages to change the buffer and trigger another save // while the first save has not returned yet. // - if (this.saveSequentializer.hasPendingSave()) { + if ((this.saveSequentializer as TaskSequentializer).hasPending()) { // {{SQL CARBON EDIT}} strict-null-check this.logService.trace(`[text file model] doSave(${versionId}) - exit - because busy saving`, this.resource.toString()); + // Indicate to the save sequentializer that we want to + // cancel the pending operation so that ours can run + // before the pending one finishes. + // Currently this will try to cancel pending save + // participants but never a pending save. + (this.saveSequentializer as TaskSequentializer).cancelPending(); // {{SQL CARBON EDIT}} strict-null-check + // Register this as the next upcoming save and return - return this.saveSequentializer.setNext(() => this.doSave(options)); + return (this.saveSequentializer as TaskSequentializer).setNext(() => this.doSave(options)); // {{SQL CARBON EDIT}} strict-null-check } // Push all edit operations to the undo stack so that the user has a chance to @@ -616,6 +623,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // In addition we update our version right after in case it changed because of a model change // // Save participants can also be skipped through API. + const saveParticipantCancellation = new CancellationTokenSource(); let saveParticipantPromise: Promise = Promise.resolve(versionId); if (this.isResolved() && this.textFileService.saveParticipant && !options.skipSaveParticipants) { const onCompleteOrError = () => { @@ -625,11 +633,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil }; this.ignoreDirtyOnModelContentChange = true; - saveParticipantPromise = this.textFileService.saveParticipant.participate(this, { reason: options.reason }).then(onCompleteOrError, onCompleteOrError); + saveParticipantPromise = this.textFileService.saveParticipant.participate(this, { reason: options.reason }, saveParticipantCancellation.token).then(onCompleteOrError, onCompleteOrError); } // mark the save participant as current pending save operation - return this.saveSequentializer.setPending(versionId, saveParticipantPromise.then(newVersionId => { + return (this.saveSequentializer as TaskSequentializer).setPending(versionId, saveParticipantPromise.then(newVersionId => { // {{SQL CARBON EDIT}} strict-null-check // We have to protect against being disposed at this point. It could be that the save() operation // was triggerd followed by a dispose() operation right after without waiting. Typically we cannot @@ -678,7 +686,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource, this.getMode())) ? ETAG_DISABLED : lastResolvedFileStat.etag, writeElevated: options.writeElevated }).then(stat => this.handleSaveSuccess(stat, versionId, options), error => this.handleSaveError(error, versionId, options))); - })); + }), () => saveParticipantCancellation.cancel()); } private handleSaveSuccess(stat: IFileStatWithMetadata, versionId: number, options: ITextFileSaveOptions): void { @@ -783,7 +791,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil case ModelState.ORPHAN: return this.inOrphanMode; case ModelState.PENDING_SAVE: - return this.saveSequentializer.hasPendingSave(); + return this.saveSequentializer.hasPending(); case ModelState.SAVED: return !this.dirty; } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 73c9b5e11c..cbc64b6f05 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -16,6 +16,7 @@ import { isUndefinedOrNull } from 'vs/base/common/types'; import { isNative } from 'vs/base/common/platform'; import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IUntitledTextEditorModelManager } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { CancellationToken } from 'vs/base/common/cancellation'; export const ITextFileService = createDecorator('textFileService'); @@ -230,7 +231,7 @@ export interface ISaveParticipant { /** * Participate in a save of a model. Allows to change the model before it is being saved to disk. */ - participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise; + participate(model: IResolvedTextFileEditorModel, context: { reason: SaveReason }, token: CancellationToken): Promise; } /** diff --git a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts index 56802a14b7..6f9df55635 100644 --- a/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/browser/textFileEditorModel.test.ts @@ -556,4 +556,34 @@ suite('Files - TextFileEditorModel', () => { await model.save(); model.dispose(); }); + + test('Save Participant, participant cancelled when saved again', async function () { + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined); + + let participations: boolean[] = []; + + accessor.textFileService.saveParticipant = { + participate: (model) => { + return timeout(10).then(() => { + participations.push(true); + }); + } + }; + + await model.load(); + + model.textEditorModel!.setValue('foo'); + const p1 = model.save(); + + model.textEditorModel!.setValue('foo 1'); + const p2 = model.save(); + + model.textEditorModel!.setValue('foo 2'); + await model.save(); + + await p1; + await p2; + assert.equal(participations.length, 1); + model.dispose(); + }); }); diff --git a/src/vs/workbench/services/textfile/test/common/saveSequenzializer.test.ts b/src/vs/workbench/services/textfile/test/common/saveSequenzializer.test.ts deleted file mode 100644 index a34a70d208..0000000000 --- a/src/vs/workbench/services/textfile/test/common/saveSequenzializer.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { timeout } from 'vs/base/common/async'; -import { SaveSequentializer } from 'vs/workbench/services/textfile/common/saveSequenzializer'; - -suite('Files - SaveSequentializer', () => { - - test('SaveSequentializer - pending basics', async function () { - const sequentializer = new SaveSequentializer(); - - assert.ok(!sequentializer.hasPendingSave()); - assert.ok(!sequentializer.hasPendingSave(2323)); - assert.ok(!sequentializer.pendingSave); - - // pending removes itself after done - await sequentializer.setPending(1, Promise.resolve()); - assert.ok(!sequentializer.hasPendingSave()); - assert.ok(!sequentializer.hasPendingSave(1)); - assert.ok(!sequentializer.pendingSave); - - // pending removes itself after done (use timeout) - sequentializer.setPending(2, timeout(1)); - assert.ok(sequentializer.hasPendingSave()); - assert.ok(sequentializer.hasPendingSave(2)); - assert.ok(!sequentializer.hasPendingSave(1)); - assert.ok(sequentializer.pendingSave); - - await timeout(2); - assert.ok(!sequentializer.hasPendingSave()); - assert.ok(!sequentializer.hasPendingSave(2)); - assert.ok(!sequentializer.pendingSave); - }); - - test('SaveSequentializer - pending and next (finishes instantly)', async function () { - const sequentializer = new SaveSequentializer(); - - let pendingDone = false; - sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return; })); - - // next finishes instantly - let nextDone = false; - const res = sequentializer.setNext(() => Promise.resolve(null).then(() => { nextDone = true; return; })); - - await res; - assert.ok(pendingDone); - assert.ok(nextDone); - }); - - test('SaveSequentializer - pending and next (finishes after timeout)', async function () { - const sequentializer = new SaveSequentializer(); - - let pendingDone = false; - sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return; })); - - // next finishes after timeout - let nextDone = false; - const res = sequentializer.setNext(() => timeout(1).then(() => { nextDone = true; return; })); - - await res; - assert.ok(pendingDone); - assert.ok(nextDone); - }); - - test('SaveSequentializer - pending and multiple next (last one wins)', async function () { - const sequentializer = new SaveSequentializer(); - - let pendingDone = false; - sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return; })); - - // next finishes after timeout - let firstDone = false; - let firstRes = sequentializer.setNext(() => timeout(2).then(() => { firstDone = true; return; })); - - let secondDone = false; - let secondRes = sequentializer.setNext(() => timeout(3).then(() => { secondDone = true; return; })); - - let thirdDone = false; - let thirdRes = sequentializer.setNext(() => timeout(4).then(() => { thirdDone = true; return; })); - - await Promise.all([firstRes, secondRes, thirdRes]); - assert.ok(pendingDone); - assert.ok(!firstDone); - assert.ok(!secondDone); - assert.ok(thirdDone); - }); -}); diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts index 03a98b654b..9af8fcb058 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -44,6 +44,11 @@ export interface IUntitledTextEditorModel extends ITextEditorModel, IModeSupport */ readonly hasAssociatedFilePath: boolean; + /** + * Wether this model has an explicit language mode or not. + */ + readonly hasModeSetExplicitly: boolean; + /** * Sets the encoding to use for this untitled model. */ @@ -150,7 +155,14 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt return this.versionId; } + private _hasModeSetExplicitly: boolean = false; + get hasModeSetExplicitly(): boolean { return this._hasModeSetExplicitly; } + setMode(mode: string): void { + + // Remember that an explicit mode was set + this._hasModeSetExplicitly = true; + let actualMode: string | undefined = undefined; if (mode === '${activeEditorLanguage}') { // support the special '${activeEditorLanguage}' mode by diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index 6ca2bf37ea..0a6a9ecaa7 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -287,12 +287,34 @@ suite('Untitled text editors', () => { const service = accessor.untitledTextEditorService; const input = instantiationService.createInstance(UntitledTextEditorInput, service.create({ mode })); + assert.ok(input.model.hasModeSetExplicitly); assert.equal(input.getMode(), mode); const model = await input.resolve(); assert.equal(model.getMode(), mode); - input.setMode('text'); + input.setMode('plaintext'); + + assert.equal(input.getMode(), PLAINTEXT_MODE_ID); + + input.dispose(); + model.dispose(); + }); + + test('remembers that mode was set explicitly', async () => { + const mode = 'untitled-input-test'; + + ModesRegistry.registerLanguage({ + id: mode, + }); + + const service = accessor.untitledTextEditorService; + const model = service.create(); + const input = instantiationService.createInstance(UntitledTextEditorInput, model); + + assert.ok(!input.model.hasModeSetExplicitly); + input.setMode('plaintext'); + assert.ok(input.model.hasModeSetExplicitly); assert.equal(input.getMode(), PLAINTEXT_MODE_ID); diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index 8a8cf155b1..c70682d411 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -16,6 +16,7 @@ import { IUpdateProvider, IUpdate } from 'vs/workbench/services/update/browser/u import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/browser/browserHostService'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; interface IResourceUriProvider { (uri: URI): URI; @@ -36,18 +37,23 @@ interface IExternalUriResolver { interface TunnelOptions { remoteAddress: { port: number, host: string }; - // The desired local port. If this port can't be used, then another will be chosen. + /** + * The desired local port. If this port can't be used, then another will be chosen. + */ localAddressPort?: number; label?: string; } -interface Tunnel { +interface Tunnel extends IDisposable { remoteAddress: { port: number, host: string }; - //The complete local address(ex. localhost:1234) + /** + * The complete local address(ex. localhost:1234) + */ localAddress: string; - // Implementers of Tunnel should fire onDidDispose when dispose is called. + /** + * Implementers of Tunnel should fire onDidDispose when dispose is called. + */ onDidDispose: Event; - dispose(): void; } interface ITunnelFactory { @@ -186,14 +192,43 @@ interface IWorkbenchConstructionOptions { readonly driver?: boolean; } +interface ICommandHandler { + (...args: any[]): void; +} + +interface IWorkbench { + + /** + * Register a command with the provided identifier and handler with + * the workbench. The command can be called from extensions using the + * `vscode.commands.executeCommand` API. + */ + registerCommand(id: string, command: ICommandHandler): IDisposable; +} + /** * Creates the workbench with the provided options in the provided container. * * @param domElement the container to create the workbench in * @param options for setting up the workbench + * + * @returns the workbench facade with additional methods to call on. */ -function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { - return main(domElement, options); +async function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise { + + // Startup workbench + await main(domElement, options); + + // Return facade + return { + registerCommand: (id: string, command: ICommandHandler): IDisposable => { + return CommandsRegistry.registerCommand(id, (accessor, ...args: any[]) => { + // we currently only pass on the arguments but not the accessor + // to the command to reduce our exposure of internal API. + command(...args); + }); + } + }; } export { @@ -202,6 +237,10 @@ export { create, IWorkbenchConstructionOptions, + // Workbench Facade + IWorkbench, + ICommandHandler, + // Basic Types URI, UriComponents, diff --git a/test/integration/browser/README.md b/test/integration/browser/README.md index 6140aab9ac..10a55f7de1 100644 --- a/test/integration/browser/README.md +++ b/test/integration/browser/README.md @@ -19,3 +19,7 @@ All integration tests run in an Electron instance. You can specify to run the te All integration tests run in a browser instance as specified by the command line arguments. Add the `--debug` flag to see a browser window with the tests running. + +## Debug + +All integration tests can be run and debugged from within VSCode (both Electron and Web) simply by selecting the related launch configuration and running them.