From 1fbb4c3095dd2345a34888cf082370f2f874bd89 Mon Sep 17 00:00:00 2001 From: Aman Khalid Date: Thu, 14 Apr 2022 13:18:52 -0400 Subject: Close #134566: Added settings for terminal tab default color/icon --- .../browser/terminalProfileResolverService.ts | 12 ++++++++++-- .../terminal/common/terminalConfiguration.ts | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts index 0849f6676b2..1554d580f33 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts @@ -16,6 +16,7 @@ import { IShellLaunchConfig, ITerminalProfile, ITerminalProfileObject, TerminalI import { IShellLaunchConfigResolveOptions, ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; import * as path from 'vs/base/common/path'; import { Codicon } from 'vs/base/common/codicons'; +import { getIconRegistry, IIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { debounce } from 'vs/base/common/decorators'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -51,6 +52,8 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro private _primaryBackendOs: OperatingSystem | undefined; + private readonly _iconRegistry: IIconRegistry = getIconRegistry(); + private _defaultProfileName: string | undefined; get defaultProfileName(): string | undefined { return this._defaultProfileName; } @@ -135,7 +138,10 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro // Verify the icon is valid, and fallback correctly to the generic terminal id if there is // an issue - shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || this._getCustomIcon(resolvedProfile.icon) || Codicon.terminal; + shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) + || this._getCustomIcon(resolvedProfile.icon) + || this._iconRegistry.getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)) + || Codicon.terminal; // Override the name if specified if (resolvedProfile.overrideName) { @@ -143,7 +149,9 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro } // Apply the color - shellLaunchConfig.color = shellLaunchConfig.color || resolvedProfile.color; + shellLaunchConfig.color = shellLaunchConfig.color + || resolvedProfile.color + || this._configurationService.getValue(TerminalSettingId.TabsDefaultColor); // Resolve useShellEnvironment based on the setting if it's not set if (shellLaunchConfig.useShellEnvironment === undefined) { diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 4020b3e6fa0..27f5698ddb8 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -9,6 +9,7 @@ import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAU import { TerminalLocationString, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { Registry } from 'vs/platform/registry/common/platform'; +import { Codicon } from 'vs/base/common/codicons'; const terminalDescriptors = '\n- ' + [ '`\${cwd}`: ' + localize("cwd", "the terminal's current working directory"), @@ -38,6 +39,27 @@ const terminalConfiguration: IConfigurationNode = { type: 'boolean', default: false }, + [TerminalSettingId.TabsDefaultColor]: { + description: localize('terminal.integrated.tabs.defaultColor', "Controls the terminal tab icon's default color."), + type: 'string', + enum: [ + 'terminal.ansiBlack', + 'terminal.ansiRed', + 'terminal.ansiGreen', + 'terminal.ansiYellow', + 'terminal.ansiBlue', + 'terminal.ansiMagenta', + 'terminal.ansiCyan', + 'terminal.ansiWhite' + ], + default: undefined, + }, + [TerminalSettingId.TabsDefaultIcon]: { + description: localize('terminal.integrated.tabs.defaultIcon', "Controls the terminal tab's default icon."), + type: 'string', + enum: Codicon.getAll().map(icon => icon.id), + default: undefined, + }, [TerminalSettingId.TabsEnabled]: { description: localize('terminal.integrated.tabs.enabled', 'Controls whether terminal tabs display as a list to the side of the terminal. When this is disabled a dropdown will display instead.'), type: 'boolean', -- cgit v1.2.3 From 71c15eac23c7cf2e32b3ed8222c6ac4d35c614ba Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Sat, 11 Jun 2022 20:57:05 -0400 Subject: Fix terminals in editor area not reloading - Closes #140429 - This isn't perfect because it just reopens then without setting the original location, but at least it reloads them. --- .../contrib/terminal/browser/terminalService.ts | 35 ++++++++++++++++++++-- .../terminal/common/remoteTerminalChannel.ts | 3 +- .../electron-sandbox/localTerminalBackend.ts | 3 +- 3 files changed, 36 insertions(+), 5 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 62d52e82cc9..4bb83addb42 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -403,8 +403,18 @@ export class TerminalService implements ITerminalService { return; } const layoutInfo = await localBackend.getTerminalLayoutInfo(); - if (layoutInfo && layoutInfo.tabs.length > 0) { - await this._recreateTerminalGroups(layoutInfo); + if (layoutInfo) { + if (layoutInfo.tabs.length > 0) { + await this._recreateTerminalGroups(layoutInfo); + } + if (layoutInfo.editorTerminals && layoutInfo.editorTerminals.length > 0) { + for (const editorTerminal of layoutInfo.editorTerminals) { + await this.createTerminal({ + config: { attachPersistentProcess: editorTerminal.terminal! }, + location: TerminalLocation.Editor + }); + } + } } // now that terminals have been restored, // attach listeners to update local state when terminals are changed @@ -642,7 +652,26 @@ export class TerminalService implements ITerminalService { return; } const tabs = this._terminalGroupService.groups.map(g => g.getLayoutInfo(g === this._terminalGroupService.activeGroup)); - const state: ITerminalsLayoutInfoById = { tabs }; + + // Save terminals in editors too + const seenPersistentProcessIds: number[] = []; + for (const t of tabs) { + for (const term of t.terminals) { + seenPersistentProcessIds.push(term.terminal); + } + } + const otherInstances = this.instances.filter(instance => typeof instance.persistentProcessId === 'number' && instance.shouldPersist && seenPersistentProcessIds.indexOf(instance.persistentProcessId) === -1); + const editorTerminals = otherInstances.map((instance) => { + instance. + return { + terminal: instance.persistentProcessId || 0 + }; + }); + + const state: ITerminalsLayoutInfoById = { + tabs, + editorTerminals + }; this._primaryBackend?.setTerminalLayoutInfo(state); } diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts index e8c7da68932..a41a5f89716 100644 --- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts @@ -266,7 +266,8 @@ export class RemoteTerminalChannelClient implements IPtyHostController { const workspace = this._workspaceContextService.getWorkspace(); const args: ISetTerminalLayoutInfoArgs = { workspaceId: workspace.id, - tabs: layout ? layout.tabs : [] + tabs: layout ? layout.tabs : [], + editorTerminals: layout ? layout.editorTerminals : [] }; return this._channel.call('$setTerminalLayoutInfo', args); } diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts index b2f01c3e853..e423da8a59f 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts @@ -199,7 +199,8 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke async setTerminalLayoutInfo(layoutInfo?: ITerminalsLayoutInfoById): Promise { const args: ISetTerminalLayoutInfoArgs = { workspaceId: this._getWorkspaceId(), - tabs: layoutInfo ? layoutInfo.tabs : [] + tabs: layoutInfo ? layoutInfo.tabs : [], + editorTerminals: layoutInfo ? layoutInfo.editorTerminals : [] }; await this._localPtyService.setTerminalLayoutInfo(args); // Store in the storage service as well to be used when reviving processes as normally this -- cgit v1.2.3 From f33aeb876f2acd410693ccd241b667ce4173fd09 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Sat, 11 Jun 2022 21:07:33 -0400 Subject: Fix accidental line of code --- src/vs/workbench/contrib/terminal/browser/terminalService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 4bb83addb42..e8b200aa2f1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -662,8 +662,7 @@ export class TerminalService implements ITerminalService { } const otherInstances = this.instances.filter(instance => typeof instance.persistentProcessId === 'number' && instance.shouldPersist && seenPersistentProcessIds.indexOf(instance.persistentProcessId) === -1); const editorTerminals = otherInstances.map((instance) => { - instance. - return { + return { terminal: instance.persistentProcessId || 0 }; }); -- cgit v1.2.3 From a084f63f4d3e5477efbcd8851008e8d996a45681 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 15 Jun 2022 10:17:53 -0700 Subject: fix issues --- src/vs/workbench/contrib/terminal/browser/terminalService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index f9ba263232c..02072ae283c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -21,6 +21,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ICreateContributedTerminalProfileOptions, IShellLaunchConfig, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalLocation, TerminalLocationString, TitleEventSource } from 'vs/platform/terminal/common/terminal'; +import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings'; import { iconForeground } from 'vs/platform/theme/common/colorRegistry'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; @@ -38,7 +39,6 @@ import { getInstanceFromResource, getTerminalUri, parseTerminalUri } from 'vs/wo import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView'; import { IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalBackend, ITerminalConfigHelper, ITerminalProcessExtHostProxy, ITerminalProfileService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; -import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -- cgit v1.2.3 From bc7344685d7213a16a1a0c43d8287d824692c4ae Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 17 Jun 2022 08:51:28 -0700 Subject: check length of editor terminals --- .../contrib/terminal/browser/terminalEditorService.ts | 2 ++ .../contrib/terminal/browser/terminalService.ts | 16 ++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts index aec68ee5d91..5a501adebd2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts @@ -93,6 +93,7 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor // Remove the terminal from the managed instances when the editor closes. This fires when // dragging and dropping to another editor or closing the editor via cmd/ctrl+w. this._register(this._editorService.onDidCloseEditor(e => { + console.log('closed editor'); const instance = e.editor instanceof TerminalEditorInput ? e.editor.terminalInstance : undefined; if (instance) { const instanceIndex = this.instances.findIndex(e => e === instance); @@ -100,6 +101,7 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor this.instances.splice(instanceIndex, 1); } } + console.log(this.instances); })); this._register(this._editorService.onDidActiveEditorChange(() => { const instance = this._editorService.activeEditor instanceof TerminalEditorInput ? this._editorService.activeEditor : undefined; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 02072ae283c..4d1220d4375 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -407,12 +407,16 @@ export class TerminalService implements ITerminalService { if (layoutInfo.tabs.length > 0) { await this._recreateTerminalGroups(layoutInfo); } - if (layoutInfo.editorTerminals && layoutInfo.editorTerminals.length > 0) { - for (const editorTerminal of layoutInfo.editorTerminals) { - await this.createTerminal({ - config: { attachPersistentProcess: editorTerminal.terminal! }, - location: TerminalLocation.Editor - }); + if (this._terminalEditorService.instances.length === 0) { + // only do this for restart because editor terminals are already restored + // on reload + if (layoutInfo.editorTerminals && layoutInfo.editorTerminals.length > 0) { + for (const editorTerminal of layoutInfo.editorTerminals) { + await this.createTerminal({ + config: { attachPersistentProcess: editorTerminal.terminal! }, + location: TerminalLocation.Editor + }); + } } } } -- cgit v1.2.3 From 7aa326e2d7a802cb8622842309e10196ce228b53 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Sat, 18 Jun 2022 15:26:59 -0400 Subject: Get info from terminal editor - The issue is `_onWillShutdown` is called before `TerminalInputSerializer` serializes, so I added `shutdownPersistentProcessId`. - When restoring the editor, it was using the wrong ID, so I added `getRevivedPtyNewId`. --- .../terminal/browser/remoteTerminalBackend.ts | 17 +++++++++ .../workbench/contrib/terminal/browser/terminal.ts | 5 +++ .../terminal/browser/terminalEditorService.ts | 3 +- .../contrib/terminal/browser/terminalInstance.ts | 8 +++-- .../terminal/browser/terminalProcessManager.ts | 2 +- .../contrib/terminal/browser/terminalService.ts | 41 +++++----------------- .../terminal/common/remoteTerminalChannel.ts | 7 ++-- .../workbench/contrib/terminal/common/terminal.ts | 1 + .../electron-sandbox/localTerminalBackend.ts | 16 +++++++-- 9 files changed, 59 insertions(+), 41 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts index dbbcea77cf9..6b304681efd 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts @@ -239,6 +239,23 @@ class RemoteTerminalBackend extends BaseTerminalBackend implements ITerminalBack return undefined; } + async attachToRevivedProcess(id: number): Promise { + if (!this._remoteTerminalChannel) { + throw new Error(`Cannot create remote terminal when there is no remote!`); + } + + try { + const newId = await this._remoteTerminalChannel.getRevivedPtyNewId(id); + if (newId === undefined) { + return undefined; + } + return await this.attachToProcess(newId); + } catch (e) { + this._logService.trace(`Couldn't attach to process ${e.message}`); + } + return undefined; + } + async listProcesses(): Promise { const terms = this._remoteTerminalChannel ? await this._remoteTerminalChannel.listProcesses() : []; return terms.map(termDto => { diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index d11e3359e8a..ecff6314eec 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -470,6 +470,11 @@ export interface ITerminalInstance { */ readonly persistentProcessId: number | undefined; + /** + * The id of a persistent process during the shutdown process + */ + shutdownPersistentProcessId: number | undefined; + /** * Whether the process should be persisted across reloads. */ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts index 5a501adebd2..5ed91cd6c12 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts @@ -281,7 +281,8 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor const inputKey = resource.path; if ('pid' in deserializedInput) { - const instance = this._terminalInstanceService.createInstance({ attachPersistentProcess: deserializedInput }, TerminalLocation.Editor); + const newDeserializedInput = { ...deserializedInput, findRevivedId: true }; + const instance = this._terminalInstanceService.createInstance({ attachPersistentProcess: newDeserializedInput }, TerminalLocation.Editor); instance.target = TerminalLocation.Editor; const input = this._instantiationService.createInstance(TerminalEditorInput, resource, instance); this._registerInstance(inputKey, input, instance); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 7f9589e2ca3..119ef061d0a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -150,6 +150,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private readonly _processManager: ITerminalProcessManager; private readonly _resource: URI; + private _shutdownPersistentProcessId: number | undefined; // Enables disposal of the xterm onKey // event when the CwdDetection capability @@ -654,8 +655,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return TerminalInstance._lastKnownCanvasDimensions; } - get persistentProcessId(): number | undefined { return this._processManager.persistentProcessId; } - get shouldPersist(): boolean { return this._processManager.shouldPersist && !this.shellLaunchConfig.isTransient; } + set shutdownPersistentProcessId(shutdownPersistentProcessId: number | undefined) { + this._shutdownPersistentProcessId = shutdownPersistentProcessId; + } + get persistentProcessId(): number | undefined { return this._processManager.persistentProcessId ?? this._shutdownPersistentProcessId; } + get shouldPersist(): boolean { return (this._processManager.shouldPersist || this._shutdownPersistentProcessId !== undefined) && !this.shellLaunchConfig.isTransient; } /** * Create xterm.js instance and attach data listeners. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index bba89cdbcee..db29d770391 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -290,7 +290,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce } } else { if (shellLaunchConfig.attachPersistentProcess) { - const result = await backend.attachToProcess(shellLaunchConfig.attachPersistentProcess.id); + const result = shellLaunchConfig.attachPersistentProcess.findRevivedId ? await backend.attachToRevivedProcess(shellLaunchConfig.attachPersistentProcess.id) : await backend.attachToProcess(shellLaunchConfig.attachPersistentProcess.id); if (result) { newProcess = result; } else { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 4d1220d4375..69ec76c20d0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -403,22 +403,8 @@ export class TerminalService implements ITerminalService { return; } const layoutInfo = await localBackend.getTerminalLayoutInfo(); - if (layoutInfo) { - if (layoutInfo.tabs.length > 0) { - await this._recreateTerminalGroups(layoutInfo); - } - if (this._terminalEditorService.instances.length === 0) { - // only do this for restart because editor terminals are already restored - // on reload - if (layoutInfo.editorTerminals && layoutInfo.editorTerminals.length > 0) { - for (const editorTerminal of layoutInfo.editorTerminals) { - await this.createTerminal({ - config: { attachPersistentProcess: editorTerminal.terminal! }, - location: TerminalLocation.Editor - }); - } - } - } + if (layoutInfo && layoutInfo.tabs.length > 0) { + await this._recreateTerminalGroups(layoutInfo); } // now that terminals have been restored, // attach listeners to update local state when terminals are changed @@ -634,8 +620,13 @@ export class TerminalService implements ITerminalService { return; } + // Force dispose of all terminal instances + const shouldPersistTerminalsForEvent = this._shouldReviveProcesses(e.reason); for (const instance of this.instances) { + if (shouldPersistTerminalsForEvent) { + instance.shutdownPersistentProcessId = instance.persistentProcessId; + } instance.dispose(); } @@ -659,24 +650,8 @@ export class TerminalService implements ITerminalService { return; } const tabs = this._terminalGroupService.groups.map(g => g.getLayoutInfo(g === this._terminalGroupService.activeGroup)); - - // Save terminals in editors too - const seenPersistentProcessIds: number[] = []; - for (const t of tabs) { - for (const term of t.terminals) { - seenPersistentProcessIds.push(term.terminal); - } - } - const otherInstances = this.instances.filter(instance => typeof instance.persistentProcessId === 'number' && instance.shouldPersist && seenPersistentProcessIds.indexOf(instance.persistentProcessId) === -1); - const editorTerminals = otherInstances.map((instance) => { - return { - terminal: instance.persistentProcessId || 0 - }; - }); - const state: ITerminalsLayoutInfoById = { - tabs, - editorTerminals + tabs }; this._primaryBackend?.setTerminalLayoutInfo(state); } diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts index a41a5f89716..1b62d1e9cb3 100644 --- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts @@ -266,8 +266,7 @@ export class RemoteTerminalChannelClient implements IPtyHostController { const workspace = this._workspaceContextService.getWorkspace(); const args: ISetTerminalLayoutInfoArgs = { workspaceId: workspace.id, - tabs: layout ? layout.tabs : [], - editorTerminals: layout ? layout.editorTerminals : [] + tabs: layout ? layout.tabs : [] }; return this._channel.call('$setTerminalLayoutInfo', args); } @@ -300,6 +299,10 @@ export class RemoteTerminalChannelClient implements IPtyHostController { return this._channel.call('$reviveTerminalProcesses', [state, dateTimeFormatLocate]); } + getRevivedPtyNewId(id: number): Promise { + return this._channel.call('$getRevivedPtyNewId', [id]); + } + serializeTerminalState(ids: number[]): Promise { return this._channel.call('$serializeTerminalState', [ids]); } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 1d3a1aa85db..04b98030a41 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -116,6 +116,7 @@ export interface ITerminalBackend { onDidRequestDetach: Event<{ requestId: number; workspaceId: string; instanceId: number }>; attachToProcess(id: number): Promise; + attachToRevivedProcess(id: number): Promise; listProcesses(): Promise; getDefaultSystemShell(osOverride?: OperatingSystem): Promise; getProfiles(profiles: unknown, defaultProfile: unknown, includeDetectedProfiles?: boolean): Promise; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts index e423da8a59f..8b9802f0abc 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts @@ -168,6 +168,19 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke return undefined; } + async attachToRevivedProcess(id: number): Promise { + try { + const newId = await this._localPtyService.getRevivedPtyNewId(id); + if (newId === undefined) { + return undefined; + } + return await this.attachToProcess(newId); + } catch (e) { + this._logService.trace(`Couldn't attach to process ${e.message}`); + } + return undefined; + } + async listProcesses(): Promise { return this._localPtyService.listProcesses(); } @@ -199,8 +212,7 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke async setTerminalLayoutInfo(layoutInfo?: ITerminalsLayoutInfoById): Promise { const args: ISetTerminalLayoutInfoArgs = { workspaceId: this._getWorkspaceId(), - tabs: layoutInfo ? layoutInfo.tabs : [], - editorTerminals: layoutInfo ? layoutInfo.editorTerminals : [] + tabs: layoutInfo ? layoutInfo.tabs : [] }; await this._localPtyService.setTerminalLayoutInfo(args); // Store in the storage service as well to be used when reviving processes as normally this -- cgit v1.2.3 From 1f37af91f0533162174ce784e023af03b6c0d7ed Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Sat, 18 Jun 2022 15:30:19 -0400 Subject: Self review cleanup --- src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts | 2 -- src/vs/workbench/contrib/terminal/browser/terminalService.ts | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts index 5ed91cd6c12..74b83274dca 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts @@ -93,7 +93,6 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor // Remove the terminal from the managed instances when the editor closes. This fires when // dragging and dropping to another editor or closing the editor via cmd/ctrl+w. this._register(this._editorService.onDidCloseEditor(e => { - console.log('closed editor'); const instance = e.editor instanceof TerminalEditorInput ? e.editor.terminalInstance : undefined; if (instance) { const instanceIndex = this.instances.findIndex(e => e === instance); @@ -101,7 +100,6 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor this.instances.splice(instanceIndex, 1); } } - console.log(this.instances); })); this._register(this._editorService.onDidActiveEditorChange(() => { const instance = this._editorService.activeEditor instanceof TerminalEditorInput ? this._editorService.activeEditor : undefined; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 69ec76c20d0..11e980b5f71 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -650,9 +650,7 @@ export class TerminalService implements ITerminalService { return; } const tabs = this._terminalGroupService.groups.map(g => g.getLayoutInfo(g === this._terminalGroupService.activeGroup)); - const state: ITerminalsLayoutInfoById = { - tabs - }; + const state: ITerminalsLayoutInfoById = { tabs }; this._primaryBackend?.setTerminalLayoutInfo(state); } -- cgit v1.2.3 From 9dcdfa421e249c14cceb2c8bcd8e9f407e738457 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Mon, 20 Jun 2022 20:41:14 -0400 Subject: Use same ID for reload to reconnect --- src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts | 5 +---- .../contrib/terminal/electron-sandbox/localTerminalBackend.ts | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts index 6b304681efd..35454bcae4e 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts @@ -245,10 +245,7 @@ class RemoteTerminalBackend extends BaseTerminalBackend implements ITerminalBack } try { - const newId = await this._remoteTerminalChannel.getRevivedPtyNewId(id); - if (newId === undefined) { - return undefined; - } + const newId = await this._remoteTerminalChannel.getRevivedPtyNewId(id) ?? id; return await this.attachToProcess(newId); } catch (e) { this._logService.trace(`Couldn't attach to process ${e.message}`); diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts index 8b9802f0abc..d61acd4c4a4 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts @@ -170,10 +170,7 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke async attachToRevivedProcess(id: number): Promise { try { - const newId = await this._localPtyService.getRevivedPtyNewId(id); - if (newId === undefined) { - return undefined; - } + const newId = await this._localPtyService.getRevivedPtyNewId(id) ?? id; return await this.attachToProcess(newId); } catch (e) { this._logService.trace(`Couldn't attach to process ${e.message}`); -- cgit v1.2.3 From 757c5f54c90bfde5406d0436df3229cb8e5d12bc Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Wed, 22 Jun 2022 00:51:33 -0500 Subject: Proposal TerminalExitStatus --- src/vs/workbench/contrib/terminal/browser/terminal.ts | 11 +++++------ src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 10 ++++++---- .../workbench/contrib/terminal/browser/terminalQuickAccess.ts | 5 +++-- src/vs/workbench/contrib/terminal/browser/terminalService.ts | 2 +- 4 files changed, 15 insertions(+), 13 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index ddaf6f211db..ea2e9200ecc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -11,7 +11,7 @@ import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; import { ITerminalCapabilityStore, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; -import { IExtensionTerminalProfile, IProcessPropertyMap, IShellIntegration, IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, ProcessPropertyType, TerminalIcon, TerminalLocation, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal'; +import { IExtensionTerminalProfile, IProcessPropertyMap, IShellIntegration, IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, ProcessPropertyType, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal'; import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; @@ -567,6 +567,8 @@ export interface ITerminalInstance { readonly exitCode: number | undefined; + readonly exitReason: TerminalExitReason | undefined; + readonly areLinksReady: boolean; /** @@ -651,12 +653,9 @@ export interface ITerminalInstance { /** * Dispose the terminal instance, removing it from the panel/service and freeing up resources. * - * @param immediate Whether the kill should be immediate or not. Immediate should only be used - * when VS Code is shutting down or in cases where the terminal dispose was user initiated. - * The immediate===false exists to cover an edge case where the final output of the terminal can - * get cut off. If immediate kill any terminal processes immediately. + * @param isShutdown Whether the kill was triggered by lifecycle shutdown */ - dispose(immediate?: boolean): void; + dispose(isShutdown?: boolean): void; /** * Inform the process that the terminal is now detached. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 4401c00feeb..1d6005c587e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -46,7 +46,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { TerminalCapabilityStoreMultiplexer } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; -import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, PosixShellType, ProcessPropertyType, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, PosixShellType, ProcessPropertyType, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal'; import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -170,6 +170,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _isVisible: boolean; private _isDisposed: boolean; private _exitCode: number | undefined; + private _exitReason: TerminalExitReason | undefined; private _skipTerminalCommands: string[]; private _shellType: TerminalShellType; private _title: string = ''; @@ -275,6 +276,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { get areLinksReady(): boolean { return this._areLinksReady; } get initialDataEvents(): string[] | undefined { return this._initialDataEvents; } get exitCode(): number | undefined { return this._exitCode; } + get exitReason(): TerminalExitReason | undefined { return this._exitReason; } get hadFocusOnExit(): boolean { return this._hadFocusOnExit; } get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; } get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; } @@ -1305,8 +1307,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return confirmation.confirmed; } - - override dispose(immediate?: boolean): void { + override dispose(isShutdown?: boolean): void { if (this._isDisposed) { return; } @@ -1345,7 +1346,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._pressAnyKeyToCloseListener = undefined; } - this._processManager.dispose(immediate); + this._exitReason = isShutdown ? TerminalExitReason.Shutdown : TerminalExitReason.Unknown; + this._processManager.dispose(); // Process manager dispose/shutdown doesn't fire process exit, trigger with undefined if it // hasn't happened yet this._onProcessExit(undefined); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts index af7953a3268..11af476412f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { matchesFuzzy } from 'vs/base/common/filters'; -import { ITerminalEditorService, ITerminalGroupService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -24,6 +24,7 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider Date: Sat, 25 Jun 2022 14:10:00 -0500 Subject: More granular TerminalExitReason --- src/vs/workbench/contrib/terminal/browser/terminal.ts | 11 +++++++---- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 4 ++-- .../contrib/terminal/browser/terminalEditorInput.ts | 6 ++++-- .../workbench/contrib/terminal/browser/terminalInstance.ts | 12 ++++++------ src/vs/workbench/contrib/terminal/browser/terminalService.ts | 10 +++++----- 5 files changed, 24 insertions(+), 19 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index ea2e9200ecc..baefdbf7a67 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -653,14 +653,17 @@ export interface ITerminalInstance { /** * Dispose the terminal instance, removing it from the panel/service and freeing up resources. * - * @param isShutdown Whether the kill was triggered by lifecycle shutdown + * @param reason The reason why the terminal is being disposed */ - dispose(isShutdown?: boolean): void; + dispose(reason?: TerminalExitReason): void; /** - * Inform the process that the terminal is now detached. + * Informs the process that the terminal is now detached and + * then disposes the terminal. + * + * @param reason The reason why the terminal is being disposed */ - detachFromProcess(): Promise; + detachProcessAndDispose(reason: TerminalExitReason): Promise; /** * Check if anything is selected in terminal. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index ed78d4a79e8..7bc8ca063a0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -28,7 +28,7 @@ import { IListService } from 'vs/platform/list/browser/listService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPickOptions, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { ITerminalProfile, TerminalLocation, TerminalSettingId, TitleEventSource } from 'vs/platform/terminal/common/terminal'; +import { ITerminalProfile, TerminalExitReason, TerminalLocation, TerminalSettingId, TitleEventSource } from 'vs/platform/terminal/common/terminal'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; @@ -1062,7 +1062,7 @@ export function registerTerminalActions() { } async run(accessor: ServicesAccessor) { const terminalService = accessor.get(ITerminalService); - await terminalService.activeInstance?.detachFromProcess(); + await terminalService.activeInstance?.detachProcessAndDispose(TerminalExitReason.User); } }); registerAction2(class extends Action2 { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts index b80d8fae41c..839209a2cca 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts @@ -13,7 +13,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ITerminalInstance, ITerminalInstanceService, terminalEditorId } from 'vs/workbench/contrib/terminal/browser/terminal'; import { getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IShellLaunchConfig, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { IShellLaunchConfig, TerminalExitReason, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ConfirmOnKill } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -167,7 +167,9 @@ export class TerminalEditorInput extends EditorInput { this._register(toDisposable(() => { if (!this._isDetached && !this._isShuttingDown) { - instance.dispose(); + // Will be ignored if triggered by onExit or onDisposed terminal events + // as disposed was already called + instance.dispose(TerminalExitReason.User); } })); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 1d6005c587e..695a359ef0e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1307,7 +1307,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return confirmation.confirmed; } - override dispose(isShutdown?: boolean): void { + override dispose(reason?: TerminalExitReason): void { if (this._isDisposed) { return; } @@ -1346,7 +1346,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._pressAnyKeyToCloseListener = undefined; } - this._exitReason = isShutdown ? TerminalExitReason.Shutdown : TerminalExitReason.Unknown; + this._exitReason = reason || TerminalExitReason.Unknown; this._processManager.dispose(); // Process manager dispose/shutdown doesn't fire process exit, trigger with undefined if it // hasn't happened yet @@ -1357,11 +1357,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { super.dispose(); } - async detachFromProcess(): Promise { + async detachProcessAndDispose(reason: TerminalExitReason): Promise { // Detach the process and dispose the instance, without the instance dispose the terminal // won't go away await this._processManager.detachFromProcess(); - this.dispose(); + this.dispose(reason); } focus(force?: boolean): void { @@ -1697,7 +1697,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } }); } else { - this.dispose(); + this.dispose(TerminalExitReason.Process); if (exitMessage) { const failedDuringLaunch = this._processManager.processState === ProcessState.KilledDuringLaunch; if (failedDuringLaunch || this._configHelper.config.showExitAlert) { @@ -1767,7 +1767,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (this._pressAnyKeyToCloseListener) { this._pressAnyKeyToCloseListener.dispose(); this._pressAnyKeyToCloseListener = undefined; - this.dispose(); + this.dispose(TerminalExitReason.Process); event.preventDefault(); } }); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 23d34d8018d..0a17ca0fc8f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -20,7 +20,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { ICreateContributedTerminalProfileOptions, IShellLaunchConfig, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalLocation, TerminalLocationString, TitleEventSource } from 'vs/platform/terminal/common/terminal'; +import { ICreateContributedTerminalProfileOptions, IShellLaunchConfig, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalExitReason, TerminalLocation, TerminalLocationString, TitleEventSource } from 'vs/platform/terminal/common/terminal'; import { iconForeground } from 'vs/platform/theme/common/colorRegistry'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; @@ -282,7 +282,7 @@ export class TerminalService implements ITerminalService { } else { this._terminalGroupService.getGroupForInstance(instanceToDetach)?.removeInstance(instanceToDetach); } - await instanceToDetach.detachFromProcess(); + await instanceToDetach.detachProcessAndDispose(TerminalExitReason.User); await this._primaryBackend?.acceptDetachInstanceReply(e.requestId, persistentProcessId); } else { // will get rejected without a persistentProcessId to attach to @@ -371,7 +371,7 @@ export class TerminalService implements ITerminalService { } return new Promise(r => { instance.onExit(() => r()); - instance.dispose(); + instance.dispose(TerminalExitReason.User); }); } @@ -615,14 +615,14 @@ export class TerminalService implements ITerminalService { const shouldPersistTerminals = this._configHelper.config.enablePersistentSessions && e.reason === ShutdownReason.RELOAD; if (shouldPersistTerminals) { for (const instance of this.instances) { - instance.detachFromProcess(); + instance.detachProcessAndDispose(TerminalExitReason.Shutdown); } return; } // Force dispose of all terminal instances for (const instance of this.instances) { - instance.dispose(true); + instance.dispose(TerminalExitReason.Shutdown); } // Clear terminal layout info only when not persisting -- cgit v1.2.3 From 2d13e2222e370d7deb3692445452027602bb24e6 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 1 Jul 2022 16:12:18 +0200 Subject: Improve layout modes and workbench layout implement MergeEditor#onDidChangeSizeConstraints and minimumWidth so that layout changes reflect better in the workbench, fixes https://github.com/microsoft/vscode/issues/151866 --- .../contrib/mergeEditor/browser/view/mergeEditor.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index ae2fcbcd7f1..74629320cd2 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -10,6 +10,7 @@ import { IAction } from 'vs/base/common/actions'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Color } from 'vs/base/common/color'; import { BugIndicatingError } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; @@ -85,8 +86,6 @@ export class MergeEditor extends AbstractTextEditor { private readonly _sessionDisposables = new DisposableStore(); private _grid!: Grid; - - private readonly input1View = this._register(this.instantiation.createInstance(InputCodeEditorView, 1)); private readonly input2View = this._register(this.instantiation.createInstance(InputCodeEditorView, 2)); private readonly inputResultView = this._register(this.instantiation.createInstance(ResultCodeEditorView)); @@ -209,6 +208,19 @@ export class MergeEditor extends AbstractTextEditor { super.dispose(); } + // --- layout constraints + + private readonly _onDidChangeSizeConstraints = new Emitter(); + override readonly onDidChangeSizeConstraints: Event = this._onDidChangeSizeConstraints.event; + + override get minimumWidth() { + return this._layoutMode.value === 'mixed' + ? this.input1View.view.minimumWidth + this.input1View.view.minimumWidth + : this.input1View.view.minimumWidth + this.input1View.view.minimumWidth + this.inputResultView.view.minimumWidth; + } + + // --- + override getTitle(): string { if (this.input) { return this.input.getName(); @@ -454,6 +466,7 @@ export class MergeEditor extends AbstractTextEditor { } this._layoutMode.value = newValue; this._ctxUsesColumnLayout.set(newValue); + this._onDidChangeSizeConstraints.fire(); } private _applyViewState(state: IMergeEditorViewState | undefined) { -- cgit v1.2.3 From b9d702a2e2287fdf880e3edcf35d67486e40913a Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 1 Jul 2022 16:37:41 +0200 Subject: add an option to discard merge changes When opening a merge editor we take a snapshot of the result model. We use that snapshort to discard all merge changes so that you also restore the init state - independent of intermediate saves, https://github.com/microsoft/vscode/issues/152841#issuecomment-1167473537 --- .../mergeEditor/browser/mergeEditorInput.ts | 31 +++++++++++++++++++--- .../mergeEditor/browser/model/mergeEditorModel.ts | 5 +++- 2 files changed, 32 insertions(+), 4 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 87aa4ac9b0c..137f5a9b992 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -22,6 +22,7 @@ import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { assertType } from 'vs/base/common/types'; export class MergeEditorInputData { constructor( @@ -190,13 +191,15 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements // manual-save: FYI and discard actions.push( localize('unhandledConflicts.manualSaveIgnore', "Save and Continue with Conflicts"), // 0 - localize('unhandledConflicts.manualSaveNoSave', "Don't Save") // 1 + localize('unhandledConflicts.discard', "Discard Merge Changes"), // 1 + localize('unhandledConflicts.manualSaveNoSave', "Don't Save"), // 2 ); } else { // auto-save: only FYI actions.push( localize('unhandledConflicts.ignore', "Continue with Conflicts"), // 0 + localize('unhandledConflicts.discard', "Discard Merge Changes"), // 1 ); } @@ -224,10 +227,32 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements if (choice === 0) { // conflicts: continue with remaining conflicts return ConfirmResult.SAVE; + + } else if (choice === 1) { + // discard: undo all changes and save original (pre-merge) state + for (const input of inputs) { + input._discardMergeChanges(); + } + return ConfirmResult.SAVE; + + } else { + // don't save + return ConfirmResult.DONT_SAVE; } + } + + private _discardMergeChanges(): void { + assertType(this._model !== undefined); - // don't save - return ConfirmResult.DONT_SAVE; + const chunks: string[] = []; + while (true) { + const chunk = this._model.resultSnapshot.read(); + if (chunk === null) { + break; + } + chunks.push(chunk); + } + this._model.result.setValue(chunks.join()); } setLanguageId(languageId: string, _setExplicitly?: boolean): void { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts index d9f61d89baf..b15973884a5 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts @@ -6,7 +6,7 @@ import { CompareResult, equals } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { ITextModel } from 'vs/editor/common/model'; +import { ITextModel, ITextSnapshot } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { autorunHandleChanges, derivedObservable, IObservable, IReader, ITransaction, keepAlive, ObservableValue, transaction, waitForState } from 'vs/workbench/contrib/audioCues/browser/observable'; @@ -120,6 +120,8 @@ export class MergeEditorModel extends EditorModel { ); }); + readonly resultSnapshot: ITextSnapshot; + constructor( readonly base: ITextModel, readonly input1: ITextModel, @@ -137,6 +139,7 @@ export class MergeEditorModel extends EditorModel { ) { super(); + this.resultSnapshot = result.createSnapshot(); this._register(keepAlive(this.modifiedBaseRangeStateStores)); this._register(keepAlive(this.modifiedBaseRangeHandlingStateStores)); this._register(keepAlive(this.input1ResultMapping)); -- cgit v1.2.3 From dc5fd78fb9701f3b9895a3224fe21721e676e8c2 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 1 Jul 2022 17:45:19 +0200 Subject: use simple SPAN elements over `IconLabel` fixes https://github.com/microsoft/vscode/issues/151036 --- .../browser/view/editors/codeEditorView.ts | 19 +++++++++++-------- .../browser/view/editors/resultCodeEditorView.ts | 9 +++++---- .../mergeEditor/browser/view/media/mergeEditor.css | 22 +++++++++++++++++++--- 3 files changed, 35 insertions(+), 15 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index e9a1b888ce0..49d4726b463 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { h } from 'vs/base/browser/dom'; +import { h, reset } from 'vs/base/browser/dom'; import { IView, IViewSize } from 'vs/base/browser/ui/grid/grid'; -import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; @@ -24,7 +24,11 @@ export abstract class CodeEditorView extends Disposable { readonly model = this._viewModel.map(m => m?.model); protected readonly htmlElements = h('div.code-view', [ - h('div.title', { $: 'title' }), + h('div.title', [ + h('span.title', { $: 'title' }), + h('span.description', { $: 'description' }), + h('span.detail', { $: 'detail' }), + ]), h('div.container', [ h('div.gutter', { $: 'gutterDiv' }), h('div', { $: 'editor' }), @@ -53,9 +57,6 @@ export abstract class CodeEditorView extends Disposable { // snap?: boolean | undefined; }; - private readonly _title = new IconLabel(this.htmlElements.title, { supportIcons: true }); - protected readonly _detail = new IconLabel(this.htmlElements.title, { supportIcons: true }); - public readonly editor = this.instantiationService.createInstance( CodeEditorWidget, this.htmlElements.editor, @@ -100,8 +101,10 @@ export abstract class CodeEditorView extends Disposable { detail: string | undefined ): void { this.editor.setModel(textModel); - this._title.setLabel(title, description); - this._detail.setLabel('', detail); + + reset(this.htmlElements.title, ...renderLabelWithIcons(title)); + reset(this.htmlElements.description, ...(description ? renderLabelWithIcons(description) : [])); + reset(this.htmlElements.detail, ...(detail ? renderLabelWithIcons(detail) : [])); this._viewModel.set(viewModel, undefined); } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts index fc6919af08a..84921d8ccd9 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts @@ -114,17 +114,18 @@ export class ResultCodeEditorView extends CodeEditorView { } const count = model.unhandledConflictsCount.read(reader); - this._detail.setLabel(count === 1 + this.htmlElements.detail.innerText = count === 1 ? localize( 'mergeEditor.remainingConflicts', - '{0} Remaining Conflict', + '{0} Conflict Remaining', count ) : localize( 'mergeEditor.remainingConflict', - '{0} Remaining Conflicts', + '{0} Conflicts Remaining ', count - )); + ); + }, 'update label')); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css b/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css index b832e88bd84..e9c0ada7df3 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css @@ -8,11 +8,27 @@ height: 30px; display: flex; align-content: center; - justify-content: space-between; } -.monaco-workbench .merge-editor .code-view > .title .monaco-icon-label { - margin: auto 0; +.monaco-workbench .merge-editor .code-view > .title>SPAN { + align-self: center; + text-overflow: ellipsis; + overflow: hidden; + padding-right: 6px; +} + +.monaco-workbench .merge-editor .code-view > .title>SPAN.title { + flex-shrink: 0; +} + +.monaco-workbench .merge-editor .code-view > .title>SPAN.description { + opacity: 0.7; + font-size: 90%; +} + +.monaco-workbench .merge-editor .code-view > .title>SPAN.detail { + margin-left: auto; + flex-shrink: 0; } .monaco-workbench .merge-editor .code-view > .container { -- cgit v1.2.3 From f4ab3480cbd3d549b020ba04e37eaa66190db176 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 1 Jul 2022 10:11:20 -0700 Subject: have edit sessions use ILocalizedString (#153933) --- .../browser/sessionSync.contribution.ts | 24 ++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts index d075977cb45..08381fc3d9c 100644 --- a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts +++ b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts @@ -10,7 +10,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; -import { ISessionSyncWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_TITLE, EditSessionSchemaVersion } from 'vs/workbench/services/sessionSync/common/sessionSync'; +import { ISessionSyncWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EditSessionSchemaVersion } from 'vs/workbench/services/sessionSync/common/sessionSync'; import { ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -44,11 +44,13 @@ registerSingleton(ISessionSyncWorkbenchService, SessionSyncWorkbenchService); const resumeLatestCommand = { id: 'workbench.experimental.editSessions.actions.resumeLatest', - title: { value: localize('resume latest', "{0}: Resume Latest Edit Session", EDIT_SESSION_SYNC_TITLE), original: 'Edit Sessions' }, + title: { value: localize('resume latest', "Resume Latest Edit Session"), original: 'Resume Latest Edit Session' }, + category: EDIT_SESSION_SYNC_CATEGORY, }; const storeCurrentCommand = { id: 'workbench.experimental.editSessions.actions.storeCurrent', - title: { value: localize('store current', "{0}: Store Current Edit Session", EDIT_SESSION_SYNC_TITLE), original: 'Edit Sessions' }, + title: { value: localize('store current', "Store Current Edit Session"), original: 'Store Current Edit Session' }, + category: EDIT_SESSION_SYNC_CATEGORY, }; const continueEditSessionCommand = { id: '_workbench.experimental.editSessions.actions.continueEditSession', @@ -56,7 +58,7 @@ const continueEditSessionCommand = { }; const openLocalFolderCommand = { id: '_workbench.experimental.editSessions.actions.continueEditSession.openLocalFolder', - title: localize('continue edit session in local folder', "Open In Local Folder"), + title: { value: localize('continue edit session in local folder', "Open In Local Folder"), original: 'Open In Local Folder' }, }; const queryParamName = 'editSessionId'; @@ -146,8 +148,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon this._register(registerAction2(class ContinueEditSessionAction extends Action2 { constructor() { super({ - id: continueEditSessionCommand.id, - title: continueEditSessionCommand.title, + ...continueEditSessionCommand, f1: true }); } @@ -181,8 +182,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon this._register(registerAction2(class ApplyLatestEditSessionAction extends Action2 { constructor() { super({ - id: resumeLatestCommand.id, - title: resumeLatestCommand.title, + ...resumeLatestCommand, menu: { id: MenuId.CommandPalette, } @@ -203,8 +203,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon this._register(registerAction2(class StoreLatestEditSessionAction extends Action2 { constructor() { super({ - id: storeCurrentCommand.id, - title: storeCurrentCommand.title, + ...storeCurrentCommand, menu: { id: MenuId.CommandPalette, } @@ -275,7 +274,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon const result = await this.dialogService.confirm({ message: localize('apply edit session warning', 'Applying your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'), type: 'warning', - title: EDIT_SESSION_SYNC_TITLE + title: EDIT_SESSION_SYNC_CATEGORY.value }); if (!result.confirmed) { return; @@ -399,8 +398,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon this._register(registerAction2(class ContinueInLocalFolderAction extends Action2 { constructor() { super({ - id: openLocalFolderCommand.id, - title: openLocalFolderCommand.title, + ...openLocalFolderCommand, precondition: IsWebContext }); } -- cgit v1.2.3 From 46234601a20b70c50873a53b3a8b8477946f846e Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 1 Jul 2022 11:04:11 -0700 Subject: Allow to specify language id in command to create a new untitled file (#153872) * Allow to specify language id in command to create a new untitled file * Make viewtype optional --- src/vs/workbench/contrib/files/browser/fileCommands.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 2641877c8cd..67ab692b603 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -628,21 +628,23 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ args: [ { isOptional: true, - name: 'viewType', - description: 'The editor view type', + name: 'New Untitled File args', + description: 'The editor view type and language ID if known', schema: { 'type': 'object', - 'required': ['viewType'], 'properties': { 'viewType': { 'type': 'string' + }, + 'languageId': { + 'type': 'string' } } } } ] }, - handler: async (accessor, args?: { viewType: string }) => { + handler: async (accessor, args?: { languageId?: string; viewType?: string }) => { const editorService = accessor.get(IEditorService); await editorService.openEditor({ @@ -650,7 +652,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ options: { override: args?.viewType, pinned: true - } + }, + languageId: args?.languageId, }); } }); -- cgit v1.2.3 From 71b996eff4cef4405d1d1cc4fe308545dac0b255 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 1 Jul 2022 14:41:48 -0700 Subject: use ILocalizedString in a few places (#153950) --- .../browser/languageDetection.contribution.ts | 2 +- .../contrib/notebook/browser/controller/editActions.ts | 2 +- .../workbench/contrib/update/browser/update.contribution.ts | 12 ++++++------ .../welcomeGettingStarted/browser/gettingStartedService.ts | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts index a931e2bb2f2..ef593cb3de1 100644 --- a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts +++ b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts @@ -123,7 +123,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: detectLanguageCommandId, - title: localize('detectlang', 'Detect Language from Content'), + title: { value: localize('detectlang', 'Detect Language from Content'), original: 'Detect Language from Content' }, f1: true, precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE.toNegated(), EditorContextKeys.editorTextFocus), keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index 7250d2dc560..ebcdf94c782 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -469,7 +469,7 @@ registerAction2(class DetectCellLanguageAction extends NotebookCellAction { constructor() { super({ id: DETECT_CELL_LANGUAGE, - title: localize('detectLanguage', 'Accept Detected Language for Cell'), + title: { value: localize('detectLanguage', 'Accept Detected Language for Cell'), original: 'Accept Detected Language for Cell' }, f1: true, precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib } diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts index 5d22660c689..1ff72278d33 100644 --- a/src/vs/workbench/contrib/update/browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts @@ -34,8 +34,8 @@ class DownloadUpdateAction extends Action2 { constructor() { super({ id: 'update.downloadUpdate', - title: localize('downloadUpdate', "Download Update"), - category: product.nameShort, + title: { value: localize('downloadUpdate', "Download Update"), original: 'Download Update' }, + category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.AvailableForDownload) }); @@ -50,8 +50,8 @@ class InstallUpdateAction extends Action2 { constructor() { super({ id: 'update.installUpdate', - title: localize('installUpdate', "Install Update"), - category: product.nameShort, + title: { value: localize('installUpdate', "Install Update"), original: 'Install Update' }, + category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloaded) }); @@ -66,8 +66,8 @@ class RestartToUpdateAction extends Action2 { constructor() { super({ id: 'update.restartToUpdate', - title: localize('restartToUpdate', "Restart to Update"), - category: product.nameShort, + title: { value: localize('restartToUpdate', "Restart to Update"), original: 'Restart to Update' }, + category: { value: product.nameShort, original: product.nameShort }, f1: true, precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready) }); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts index 74b541681a6..dcbddfc4097 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts @@ -691,8 +691,8 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'resetGettingStartedProgress', - category: 'Developer', - title: 'Reset Welcome Page Walkthrough Progress', + category: { original: 'Developer', value: localize('developer', "Developer") }, + title: { original: 'Reset Welcome Page Walkthrough Progress', value: localize('resetWelcomePageWalkthroughProgress', "Reset Welcome Page Walkthrough Progress") }, f1: true }); } -- cgit v1.2.3 From c7a100ff1bf942cc94b9476a7652a1f9785cf22d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 3 Jul 2022 08:28:20 -0700 Subject: Add missing register for onData listener --- .../contrib/terminal/browser/xterm/commandNavigationAddon.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts index 08174e3b53c..83de46d909f 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts @@ -34,9 +34,9 @@ export class CommandNavigationAddon extends Disposable implements ICommandTracke activate(terminal: Terminal): void { this._terminal = terminal; - this._terminal.onData(() => { + this._register(this._terminal.onData(() => { this._currentMarker = Boundary.Bottom; - }); + })); } constructor( -- cgit v1.2.3 From c01da07ec15759e8bb532468b2cb0f1fcf0eba93 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Sun, 3 Jul 2022 19:07:03 +0200 Subject: Fixes #153898. --- .../mergeEditor/browser/view/editorGutter.ts | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts index 2b208650738..08a13900476 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts @@ -6,7 +6,6 @@ import { h } from 'vs/base/browser/dom'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; @@ -68,15 +67,13 @@ export class EditorGutter extends D const visibleRange2 = new LineRange( visibleRange.startLineNumber, visibleRange.endLineNumber - visibleRange.startLineNumber - ); + ).deltaEnd(1); const gutterItems = this.itemProvider.getIntersectingGutterItems( visibleRange2, reader ); - const lineHeight = this._editor.getOptions().get(EditorOption.lineHeight); - for (const gutterItem of gutterItems) { if (!gutterItem.range.touches(visibleRange2)) { continue; @@ -99,19 +96,10 @@ export class EditorGutter extends D } const top = - (gutterItem.range.startLineNumber === 1 - ? -lineHeight - : this._editor.getTopForLineNumber( - gutterItem.range.startLineNumber - 1 - )) - - scrollTop + - lineHeight; - - const bottom = ( - gutterItem.range.endLineNumberExclusive <= this._editor.getModel()!.getLineCount() - ? this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive) - : this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive - 1) + lineHeight - ) - scrollTop; + gutterItem.range.startLineNumber <= this._editor.getModel()!.getLineCount() + ? this._editor.getTopForLineNumber(gutterItem.range.startLineNumber, true) - scrollTop + : this._editor.getBottomForLineNumber(gutterItem.range.startLineNumber - 1, false) - scrollTop; + const bottom = this._editor.getBottomForLineNumber(gutterItem.range.endLineNumberExclusive - 1, true) - scrollTop; const height = bottom - top; -- cgit v1.2.3 From 20324ac1370af77899d239d26000b3cd32ff4981 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Sun, 3 Jul 2022 19:06:40 +0200 Subject: navigation now only considers actual conflicts. --- .../mergeEditor/browser/commands/commands.ts | 4 +-- .../contrib/mergeEditor/browser/view/viewModel.ts | 29 +++++++++++++++------- 2 files changed, 22 insertions(+), 11 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 4fa542c981d..375c202ea6a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -175,7 +175,7 @@ export class GoToNextConflict extends Action2 { run(accessor: ServicesAccessor): void { const { activeEditorPane } = accessor.get(IEditorService); if (activeEditorPane instanceof MergeEditor) { - activeEditorPane.viewModel.get()?.goToNextConflict(); + activeEditorPane.viewModel.get()?.goToNextModifiedBaseRange(true); } } } @@ -200,7 +200,7 @@ export class GoToPreviousConflict extends Action2 { run(accessor: ServicesAccessor): void { const { activeEditorPane } = accessor.get(IEditorService); if (activeEditorPane instanceof MergeEditor) { - activeEditorPane.viewModel.get()?.goToPreviousConflict(); + activeEditorPane.viewModel.get()?.goToPreviousModifiedBaseRange(true); } } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts index b53549fea4b..3f90643ea4a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts @@ -3,13 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { findLast, lastOrDefault } from 'vs/base/common/arrays'; +import { findLast } from 'vs/base/common/arrays'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { derivedObservable, derivedObservableWithWritableCache, IReader, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; import { ModifiedBaseRange, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange'; -import { elementAtOrUndefined } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { CodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView'; import { InputCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView'; import { ResultCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView'; @@ -78,7 +77,7 @@ export class MergeEditorViewModel { this.model.setState(baseRange, state, true, tx); } - public goToConflict(getModifiedBaseRange: (editor: CodeEditorView, curLineNumber: number) => ModifiedBaseRange | undefined): void { + private goToConflict(getModifiedBaseRange: (editor: CodeEditorView, curLineNumber: number) => ModifiedBaseRange | undefined): void { const lastFocusedEditor = this.lastFocusedEditor.get(); if (!lastFocusedEditor) { return; @@ -98,23 +97,35 @@ export class MergeEditorViewModel { } } - public goToNextConflict(): void { + public goToNextModifiedBaseRange(onlyConflicting: boolean): void { this.goToConflict( (e, l) => this.model.modifiedBaseRanges .get() - .find((r) => this.getRange(e, r, undefined).startLineNumber > l) || - elementAtOrUndefined(this.model.modifiedBaseRanges.get(), 0) + .find( + (r) => + (!onlyConflicting || r.isConflicting) && + this.getRange(e, r, undefined).startLineNumber > l + ) || + this.model.modifiedBaseRanges + .get() + .find((r) => !onlyConflicting || r.isConflicting) ); } - public goToPreviousConflict(): void { + public goToPreviousModifiedBaseRange(onlyConflicting: boolean): void { this.goToConflict( (e, l) => findLast( this.model.modifiedBaseRanges.get(), - (r) => this.getRange(e, r, undefined).endLineNumberExclusive < l - ) || lastOrDefault(this.model.modifiedBaseRanges.get()) + (r) => + (!onlyConflicting || r.isConflicting) && + this.getRange(e, r, undefined).endLineNumberExclusive < l + ) || + findLast( + this.model.modifiedBaseRanges.get(), + (r) => !onlyConflicting || r.isConflicting + ) ); } -- cgit v1.2.3 From fc90795932e82ab75b2a69d52f6103a3a86f05a3 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 1 Jul 2022 18:26:11 +0200 Subject: Use middle click on a checkbox to toggle its state. --- .../browser/view/editors/inputCodeEditorView.ts | 32 ++++++++++++++++++---- 1 file changed, 27 insertions(+), 5 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index 1610a9a0b0d..aadd274e6e1 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -140,6 +140,21 @@ export class InputCodeEditorView extends CodeEditorView { .withInputValue(this.inputNumber, value), tx ), + toggleBothSides() { + transaction(tx => { + const state = model + .getState(baseRange) + .get(); + model.setState( + baseRange, + state + .toggle(inputNumber) + .toggle(inputNumber === 1 ? 2 : 1), + true, + tx + ); + }); + }, getContextMenuActions: () => { const state = model.getState(baseRange).get(); const handled = model.isHandled(baseRange).get(); @@ -233,6 +248,7 @@ export interface ModifiedBaseRangeGutterItemInfo extends IGutterItemInfo { enabled: IObservable; toggleState: IObservable; setState(value: boolean, tx: ITransaction): void; + toggleBothSides(): void; getContextMenuActions(): readonly IAction[]; } @@ -257,22 +273,28 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt this._register( dom.addDisposableListener(checkBox.domNode, dom.EventType.MOUSE_DOWN, (e) => { - if (e.button === 2) { - const item = this.item.get(); - if (!item) { - return; - } + const item = this.item.get(); + if (!item) { + return; + } + if (e.button === /* Right */ 2) { contextMenuService.showContextMenu({ getAnchor: () => checkBox.domNode, getActions: item.getContextMenuActions, }); + e.stopPropagation(); + e.preventDefault(); + } else if (e.button === /* Middle */ 1) { + item.toggleBothSides(); + e.stopPropagation(); e.preventDefault(); } }) ); + checkBox.domNode.classList.add('accept-conflict-group'); this._register( -- cgit v1.2.3 From 63d90d3a34697d2a008d45da3a6460ec9a602352 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Sun, 3 Jul 2022 19:45:01 +0200 Subject: Moves observables to base/common. Adds observable tests & logging. --- .../browser/audioCueLineFeatureContribution.ts | 9 +- .../contrib/audioCues/browser/audioCueService.ts | 10 +- .../contrib/audioCues/browser/observable.ts | 718 +-------------------- .../audioCues/test/browser/observable.test.ts | 468 ++++++++++++++ 4 files changed, 512 insertions(+), 693 deletions(-) create mode 100644 src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts index 3c348baa40a..edbdd468675 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts @@ -96,6 +96,7 @@ export class AudioCueLineFeatureContribution const curLineNumber = observableFromEvent( editor.onDidChangeCursorPosition, (args) => { + /** @description editor.onDidChangeCursorPosition (caused by user) */ if ( args && args.reason !== CursorChangeReason.Explicit && @@ -193,7 +194,7 @@ class MarkerLineFeature implements LineFeature { Event.filter(this.markerService.onMarkerChanged, (changedUris) => changedUris.some((u) => u.toString() === model.uri.toString()) ), - () => ({ + () => /** @description this.markerService.onMarkerChanged */({ isPresent: (lineNumber) => { const hasMarker = this.markerService .read({ resource: model.uri }) @@ -245,7 +246,7 @@ class BreakpointLineFeature implements LineFeature { getObservableState(editor: ICodeEditor, model: ITextModel): IObservable { return observableFromEvent( this.debugService.getModel().onDidChangeBreakpoints, - () => ({ + () => /** @description debugService.getModel().onDidChangeBreakpoints */({ isPresent: (lineNumber) => { const breakpoints = this.debugService .getModel() @@ -271,12 +272,12 @@ class InlineCompletionLineFeature implements LineFeature { const activeGhostText = observableFromEvent( ghostTextController.onActiveModelDidChange, - () => ghostTextController.activeModel + () => /** @description ghostTextController.onActiveModelDidChange */ ghostTextController.activeModel ).map((activeModel) => ( activeModel ? observableFromEvent( activeModel.inlineCompletionsModel.onDidChange, - () => activeModel.inlineCompletionsModel.ghostText + () => /** @description activeModel.inlineCompletionsModel.onDidChange */ activeModel.inlineCompletionsModel.ghostText ) : undefined )); diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts index dcac8bf037b..f313933a8ec 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts @@ -9,7 +9,7 @@ import { FileAccess } from 'vs/base/common/network'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { observableFromEvent, IObservable, LazyDerived } from 'vs/workbench/contrib/audioCues/browser/observable'; +import { observableFromEvent, IObservable, derivedObservable } from 'vs/workbench/contrib/audioCues/browser/observable'; import { Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; @@ -29,7 +29,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { private readonly screenReaderAttached = observableFromEvent( this.accessibilityService.onDidChangeScreenReaderOptimized, - () => this.accessibilityService.isScreenReaderOptimized() + () => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized() ); constructor( @@ -85,7 +85,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { Event.filter(this.configurationService.onDidChangeConfiguration, (e) => e.affectsConfiguration('audioCues.enabled') ), - () => this.configurationService.getValue<'on' | 'off' | 'auto'>('audioCues.enabled') + () => /** @description config: audioCues.enabled */ this.configurationService.getValue<'on' | 'off' | 'auto'>('audioCues.enabled') ); private readonly isEnabledCache = new Cache((cue: AudioCue) => { @@ -95,7 +95,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { ), () => this.configurationService.getValue<'on' | 'off' | 'auto'>(cue.settingsKey) ); - return new LazyDerived(reader => { + return derivedObservable('audio cue enabled', reader => { const setting = settingObservable.read(reader); if ( setting === 'on' || @@ -113,7 +113,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { } return false; - }, 'audio cue enabled'); + }); }); public isEnabled(cue: AudioCue): IObservable { diff --git a/src/vs/workbench/contrib/audioCues/browser/observable.ts b/src/vs/workbench/contrib/audioCues/browser/observable.ts index 2ad62412411..37d2f8a97c8 100644 --- a/src/vs/workbench/contrib/audioCues/browser/observable.ts +++ b/src/vs/workbench/contrib/audioCues/browser/observable.ts @@ -3,688 +3,38 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; -import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; - -export interface IObservable { - _change: TChange; - - /** - * Reads the current value. - * - * This causes a recomputation if needed. - * Calling this method forces changes to propagate to observers during update operations. - * Must not be called from {@link IObserver.handleChange}. - */ - get(): T; - - /** - * Registers an observer. - * - * Calls {@link IObserver.handleChange} immediately after a change is noticed. - * Might happen while someone calls {@link IObservable.get} or {@link IObservable.read}. - */ - subscribe(observer: IObserver): void; - unsubscribe(observer: IObserver): void; - - /** - * Calls {@link IObservable.get} and then {@link IReader.handleBeforeReadObservable}. - */ - read(reader: IReader): T; - - map(fn: (value: T) => TNew): IObservable; -} - -export interface IReader { - /** - * Reports an observable that was read. - * - * Is called by `Observable.read`. - */ - handleBeforeReadObservable(observable: IObservable): void; -} - -export interface IObserver { - /** - * Indicates that an update operation is about to begin. - * - * During an update, invariants might not hold for subscribed observables and - * change events might be delayed. - * However, all changes must be reported before all update operations are over. - */ - beginUpdate(observable: IObservable): void; - - /** - * Is called by a subscribed observable immediately after it notices a change. - * - * When {@link IObservable.get} returns and no change has been reported, - * there has been no change for that observable. - * - * Implementations must not call into other observables! - * The change should be processed when {@link IObserver.endUpdate} is called. - */ - handleChange(observable: IObservable, change: TChange): void; - - /** - * Indicates that an update operation has completed. - */ - endUpdate(observable: IObservable): void; -} - -export interface ISettable { - set(value: T, transaction: ITransaction | undefined, change: TChange): void; -} - -export interface ITransaction { - /** - * Calls `Observer.beginUpdate` immediately - * and `Observer.endUpdate` when the transaction is complete. - */ - updateObserver( - observer: IObserver, - observable: IObservable - ): void; -} - -// === Base === -export abstract class ConvenientObservable implements IObservable { - get _change(): TChange { return null!; } - - public abstract get(): T; - public abstract subscribe(observer: IObserver): void; - public abstract unsubscribe(observer: IObserver): void; - - public read(reader: IReader): T { - reader.handleBeforeReadObservable(this); - return this.get(); - } - - public map(fn: (value: T) => TNew): IObservable { - return new LazyDerived((reader) => fn(this.read(reader)), '(mapped)'); - } -} - -export abstract class BaseObservable extends ConvenientObservable { - protected readonly observers = new Set(); - - public subscribe(observer: IObserver): void { - const len = this.observers.size; - this.observers.add(observer); - if (len === 0) { - this.onFirstObserverSubscribed(); - } - } - - public unsubscribe(observer: IObserver): void { - const deleted = this.observers.delete(observer); - if (deleted && this.observers.size === 0) { - this.onLastObserverUnsubscribed(); - } - } - - protected onFirstObserverSubscribed(): void { } - protected onLastObserverUnsubscribed(): void { } -} - -export function transaction(fn: (tx: ITransaction) => void) { - const tx = new TransactionImpl(); - try { - fn(tx); - } finally { - tx.finish(); - } -} - -class TransactionImpl implements ITransaction { - private readonly finishActions = new Array<() => void>(); - - public updateObserver( - observer: IObserver, - observable: IObservable - ): void { - this.finishActions.push(function () { - observer.endUpdate(observable); - }); - observer.beginUpdate(observable); - } - - public finish(): void { - for (const action of this.finishActions) { - action(); - } - } -} - -export class ObservableValue - extends BaseObservable - implements ISettable -{ - private value: T; - - constructor(initialValue: T, public readonly name: string) { - super(); - this.value = initialValue; - } - - public get(): T { - return this.value; - } - - public set(value: T, tx: ITransaction | undefined, change: TChange): void { - if (this.value === value) { - return; - } - - if (!tx) { - transaction((tx) => { - this.set(value, tx, change); - }); - return; - } - - this.value = value; - - for (const observer of this.observers) { - tx.updateObserver(observer, this); - observer.handleChange(this, change); - } - } -} - -export function constObservable(value: T): IObservable { - return new ConstObservable(value); -} - -class ConstObservable extends ConvenientObservable { - constructor(private readonly value: T) { - super(); - } - - public get(): T { - return this.value; - } - public subscribe(observer: IObserver): void { - // NO OP - } - public unsubscribe(observer: IObserver): void { - // NO OP - } -} - -// == autorun == -export function autorun(fn: (reader: IReader) => void, name: string): IDisposable { - return new AutorunObserver(fn, name, undefined); -} - -interface IChangeContext { - readonly changedObservable: IObservable; - readonly change: unknown; - - didChange(observable: IObservable): this is { change: TChange }; -} - -export function autorunHandleChanges( - name: string, - options: { - /** - * Returns if this change should cause a re-run of the autorun. - */ - handleChange: (context: IChangeContext) => boolean; - }, - fn: (reader: IReader) => void -): IDisposable { - return new AutorunObserver(fn, name, options.handleChange); -} - -export function autorunWithStore( - fn: (reader: IReader, store: DisposableStore) => void, - name: string -): IDisposable { - const store = new DisposableStore(); - const disposable = autorun( - reader => { - store.clear(); - fn(reader, store); - }, - name - ); - return toDisposable(() => { - disposable.dispose(); - store.dispose(); - }); -} - -export class AutorunObserver implements IObserver, IReader, IDisposable { - public needsToRun = true; - private updateCount = 0; - - /** - * The actual dependencies. - */ - private _dependencies = new Set>(); - public get dependencies() { - return this._dependencies; - } - - /** - * Dependencies that have to be removed when {@link runFn} ran through. - */ - private staleDependencies = new Set>(); - - constructor( - private readonly runFn: (reader: IReader) => void, - public readonly name: string, - private readonly _handleChange: ((context: IChangeContext) => boolean) | undefined - ) { - this.runIfNeeded(); - } - - public handleBeforeReadObservable(observable: IObservable) { - this._dependencies.add(observable); - if (!this.staleDependencies.delete(observable)) { - observable.subscribe(this); - } - } - - public handleChange(observable: IObservable, change: TChange): void { - const shouldReact = this._handleChange ? this._handleChange({ - changedObservable: observable, - change, - didChange: o => o === observable as any, - }) : true; - this.needsToRun = this.needsToRun || shouldReact; - - if (this.updateCount === 0) { - this.runIfNeeded(); - } - } - - public beginUpdate() { - this.updateCount++; - } - - public endUpdate() { - this.updateCount--; - if (this.updateCount === 0) { - this.runIfNeeded(); - } - } - - private runIfNeeded(): void { - if (!this.needsToRun) { - return; - } - // Assert: this.staleDependencies is an empty set. - const emptySet = this.staleDependencies; - this.staleDependencies = this._dependencies; - this._dependencies = emptySet; - - this.needsToRun = false; - - try { - this.runFn(this); - } finally { - // We don't want our observed observables to think that they are (not even temporarily) not being observed. - // Thus, we only unsubscribe from observables that are definitely not read anymore. - for (const o of this.staleDependencies) { - o.unsubscribe(this); - } - this.staleDependencies.clear(); - } - } - - public dispose() { - for (const o of this._dependencies) { - o.unsubscribe(this); - } - this._dependencies.clear(); - } -} - -export namespace autorun { - export const Observer = AutorunObserver; -} -export function autorunDelta( - name: string, - observable: IObservable, - handler: (args: { lastValue: T | undefined; newValue: T }) => void -): IDisposable { - let _lastValue: T | undefined; - return autorun((reader) => { - const newValue = observable.read(reader); - const lastValue = _lastValue; - _lastValue = newValue; - handler({ lastValue, newValue }); - }, name); -} - - -// == Lazy Derived == - -export function derivedObservable(name: string, computeFn: (reader: IReader) => T): IObservable { - return new LazyDerived(computeFn, name); -} -export class LazyDerived extends ConvenientObservable { - private readonly observer: LazyDerivedObserver; - - constructor(computeFn: (reader: IReader) => T, name: string) { - super(); - this.observer = new LazyDerivedObserver(computeFn, name, this); - } - - public subscribe(observer: IObserver): void { - this.observer.subscribe(observer); - } - - public unsubscribe(observer: IObserver): void { - this.observer.unsubscribe(observer); - } - - public override read(reader: IReader): T { - return this.observer.read(reader); - } - - public get(): T { - return this.observer.get(); - } -} - -/** - * @internal - */ -class LazyDerivedObserver - extends BaseObservable - implements IReader, IObserver { - private hadValue = false; - private hasValue = false; - private value: T | undefined = undefined; - private updateCount = 0; - - private _dependencies = new Set>(); - public get dependencies(): ReadonlySet> { - return this._dependencies; - } - - /** - * Dependencies that have to be removed when {@link runFn} ran through. - */ - private staleDependencies = new Set>(); - - constructor( - private readonly computeFn: (reader: IReader) => T, - public readonly name: string, - private readonly actualObservable: LazyDerived, - ) { - super(); - } - - protected override onLastObserverUnsubscribed(): void { - /** - * We are not tracking changes anymore, thus we have to assume - * that our cache is invalid. - */ - this.hasValue = false; - this.hadValue = false; - this.value = undefined; - for (const d of this._dependencies) { - d.unsubscribe(this); - } - this._dependencies.clear(); - } - - public handleBeforeReadObservable(observable: IObservable) { - this._dependencies.add(observable); - if (!this.staleDependencies.delete(observable)) { - observable.subscribe(this); - } - } - - public handleChange() { - if (this.hasValue) { - this.hadValue = true; - this.hasValue = false; - } - - // Not in transaction: Recompute & inform observers immediately - if (this.updateCount === 0 && this.observers.size > 0) { - this.get(); - } - - // Otherwise, recompute in `endUpdate` or on demand. - } - - public beginUpdate() { - if (this.updateCount === 0) { - for (const r of this.observers) { - r.beginUpdate(this); - } - } - this.updateCount++; - } - - public endUpdate() { - this.updateCount--; - if (this.updateCount === 0) { - if (this.observers.size > 0) { - // Propagate invalidation - this.get(); - } - - for (const r of this.observers) { - r.endUpdate(this); - } - } - } - - public get(): T { - if (this.observers.size === 0) { - // Cache is not valid and don't refresh the cache. - // Observables should not be read in non-reactive contexts. - return this.computeFn(this); - } - - if (this.updateCount > 0 && this.hasValue) { - // Refresh dependencies - for (const d of this._dependencies) { - // Maybe `.get()` triggers `handleChange`? - d.get(); - if (!this.hasValue) { - // The other dependencies will refresh on demand - break; - } - } - } - - if (!this.hasValue) { - const emptySet = this.staleDependencies; - this.staleDependencies = this._dependencies; - this._dependencies = emptySet; - - const oldValue = this.value; - try { - this.value = this.computeFn(this); - } finally { - // We don't want our observed observables to think that they are (not even temporarily) not being observed. - // Thus, we only unsubscribe from observables that are definitely not read anymore. - for (const o of this.staleDependencies) { - o.unsubscribe(this); - } - this.staleDependencies.clear(); - } - - this.hasValue = true; - if (this.hadValue && oldValue !== this.value) { - for (const r of this.observers) { - r.handleChange(this.actualObservable, undefined); - } - } - } - return this.value!; - } -} - -export namespace LazyDerived { - export const Observer = LazyDerivedObserver; -} - -export function observableFromPromise(promise: Promise): IObservable<{ value?: T }> { - const observable = new ObservableValue<{ value?: T }>({}, 'promiseValue'); - promise.then((value) => { - observable.set({ value }, undefined); - }); - return observable; -} - -export function waitForState(observable: IObservable, predicate: (state: T) => state is TState): Promise; -export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise; -export function waitForState(observable: IObservable, predicate: (state: T) => boolean): Promise { - return new Promise(resolve => { - const d = autorun(reader => { - const currentState = observable.read(reader); - if (predicate(currentState)) { - d.dispose(); - resolve(currentState); - } - }, 'waitForState'); - }); -} - -export function observableFromEvent( - event: Event, - getValue: (args: TArgs | undefined) => T -): IObservable { - return new FromEventObservable(event, getValue); -} - -class FromEventObservable extends BaseObservable { - private value: T | undefined; - private hasValue = false; - private subscription: IDisposable | undefined; - - constructor( - private readonly event: Event, - private readonly getValue: (args: TArgs | undefined) => T - ) { - super(); - } - - protected override onFirstObserverSubscribed(): void { - this.subscription = this.event(this.handleEvent); - } - - private readonly handleEvent = (args: TArgs | undefined) => { - const newValue = this.getValue(args); - if (this.value !== newValue) { - this.value = newValue; - - if (this.hasValue) { - transaction(tx => { - for (const o of this.observers) { - tx.updateObserver(o, this); - o.handleChange(this, undefined); - } - }); - } - this.hasValue = true; - } - }; - - protected override onLastObserverUnsubscribed(): void { - this.subscription!.dispose(); - this.subscription = undefined; - this.hasValue = false; - this.value = undefined; - } - - public get(): T { - if (this.subscription) { - if (!this.hasValue) { - this.handleEvent(undefined); - } - return this.value!; - } else { - // no cache, as there are no subscribers to clean it up - return this.getValue(undefined); - } - } -} - -export namespace observableFromEvent { - export const Observer = FromEventObservable; -} - -export function debouncedObservable(observable: IObservable, debounceMs: number, disposableStore: DisposableStore): IObservable { - const debouncedObservable = new ObservableValue(undefined, 'debounced'); - - let timeout: any = undefined; - - disposableStore.add(autorun(reader => { - const value = observable.read(reader); - - if (timeout) { - clearTimeout(timeout); - } - timeout = setTimeout(() => { - transaction(tx => { - debouncedObservable.set(value, tx); - }); - }, debounceMs); - - }, 'debounce')); - - return debouncedObservable; -} - -export function wasEventTriggeredRecently(event: Event, timeoutMs: number, disposableStore: DisposableStore): IObservable { - const observable = new ObservableValue(false, 'triggeredRecently'); - - let timeout: any = undefined; - - disposableStore.add(event(() => { - observable.set(true, undefined); - - if (timeout) { - clearTimeout(timeout); - } - timeout = setTimeout(() => { - observable.set(false, undefined); - }, timeoutMs); - })); - - return observable; -} - -/** - * This ensures the observable is kept up-to-date. - * This is useful when the observables `get` method is used. -*/ -export function keepAlive(observable: IObservable): IDisposable { - return autorun(reader => { - observable.read(reader); - }, 'keep-alive'); -} - -export function derivedObservableWithCache(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable { - let lastValue: T | undefined = undefined; - const observable = derivedObservable(name, reader => { - lastValue = computeFn(reader, lastValue); - return lastValue; - }); - return observable; -} - -export function derivedObservableWithWritableCache(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable & { clearCache(transaction: ITransaction): void } { - let lastValue: T | undefined = undefined; - const counter = new ObservableValue(0, 'counter'); - const observable = derivedObservable(name, reader => { - counter.read(reader); - lastValue = computeFn(reader, lastValue); - return lastValue; - }); - return Object.assign(observable, { - clearCache: (transaction: ITransaction) => { - lastValue = undefined; - counter.set(counter.get() + 1, transaction); - }, - }); +import { IDisposable } from 'vs/base/common/lifecycle'; +import * as observable from 'vs/base/common/observable'; +export { + observableFromEvent, + autorunWithStore, + IObservable, + transaction, + ITransaction, + autorunDelta, + constObservable, + observableFromPromise, + wasEventTriggeredRecently, + debouncedObservable, + autorunHandleChanges, + waitForState, + keepAlive, + IReader, + derivedObservableWithCache, + derivedObservableWithWritableCache, +} from 'vs/base/common/observable'; +import * as observableValue from 'vs/base/common/observableImpl/base'; + +export function autorun(fn: (reader: observable.IReader) => void, name: string): IDisposable { + return observable.autorun(name, fn); +} + +export class ObservableValue extends observableValue.ObservableValue { + constructor(initialValue: T, name: string) { + super(name, initialValue); + } +} + +export function derivedObservable(name: string, computeFn: (reader: observable.IReader) => T): observable.IObservable { + return observable.derived(name, computeFn); } diff --git a/src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts b/src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts new file mode 100644 index 00000000000..646464cb636 --- /dev/null +++ b/src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts @@ -0,0 +1,468 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Emitter } from 'vs/base/common/event'; +import { autorun, autorun2, BaseObservable, derivedObservable, IObserver, ITransaction, observableFromEvent, observableValue, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; + +suite('observable integration', () => { + test('basic observable + autorun', () => { + const log = new Log(); + const observable = new ObservableValue(0, 'MyObservableValue'); + + autorun((reader) => { + log.log(`value: ${observable.read(reader)}`); + }, 'MyAutorun'); + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 0']); + + observable.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 1']); + + observable.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + transaction((tx) => { + observable.set(2, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable.set(3, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), ['value: 3']); + }); + + test('basic computed + autorun', () => { + const log = new Log(); + const observable1 = new ObservableValue(0, 'MyObservableValue1'); + const observable2 = new ObservableValue(0, 'MyObservableValue2'); + + const computed = derivedObservable('computed', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + autorun((reader) => { + log.log(`value: ${computed.read(reader)}`); + }, 'MyAutorun'); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 0 + 0 = 0', + 'value: 0', + ]); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 1 + 0 = 1', + 'value: 1', + ]); + + observable2.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 1 + 1 = 2', + 'value: 2', + ]); + + transaction((tx) => { + observable1.set(5, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable2.set(5, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 5 + 5 = 10', + 'value: 10', + ]); + + transaction((tx) => { + observable1.set(6, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable2.set(4, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), ['recompute: 6 + 4 = 10']); + }); + + test('read during transaction', () => { + const log = new Log(); + const observable1 = new ObservableValue(0, 'MyObservableValue1'); + const observable2 = new ObservableValue(0, 'MyObservableValue2'); + + const computed = derivedObservable('computed', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + autorun((reader) => { + log.log(`value: ${computed.read(reader)}`); + }, 'MyAutorun'); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: 0 + 0 = 0', + 'value: 0', + ]); + + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['computed is 0']); + + transaction((tx) => { + observable1.set(-1, tx); + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: -1 + 0 = -1', + 'computed is -1', + ]); + + log.log(`computed is ${computed.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['computed is -1']); + + observable2.set(1, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute: -1 + 1 = 0', + 'value: 0', + ]); + }); + + test('topological order', () => { + const log = new Log(); + const observable1 = new ObservableValue(0, 'MyObservableValue1'); + const observable2 = new ObservableValue(0, 'MyObservableValue2'); + + const computed1 = derivedObservable('computed1', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute1: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + const computed2 = derivedObservable('computed2', (reader) => { + const value1 = computed1.read(reader); + const value2 = observable1.read(reader); + const value3 = observable2.read(reader); + const sum = value1 + value2 + value3; + log.log(`recompute2: ${value1} + ${value2} + ${value3} = ${sum}`); + return sum; + }); + + const computed3 = derivedObservable('computed3', (reader) => { + const value1 = computed2.read(reader); + const value2 = observable1.read(reader); + const value3 = observable2.read(reader); + const sum = value1 + value2 + value3; + log.log(`recompute3: ${value1} + ${value2} + ${value3} = ${sum}`); + return sum; + }); + + autorun((reader) => { + log.log(`value: ${computed3.read(reader)}`); + }, 'MyAutorun'); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 0 + 0 = 0', + 'recompute2: 0 + 0 + 0 = 0', + 'recompute3: 0 + 0 + 0 = 0', + 'value: 0', + ]); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 + 0 = 1', + 'recompute2: 1 + 1 + 0 = 2', + 'recompute3: 2 + 1 + 0 = 3', + 'value: 3', + ]); + + transaction((tx) => { + observable1.set(2, tx); + log.log(`computed2: ${computed2.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 2 + 0 = 2', + 'recompute2: 2 + 2 + 0 = 4', + 'computed2: 4', + ]); + + observable1.set(3, tx); + log.log(`computed2: ${computed2.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 3 + 0 = 3', + 'recompute2: 3 + 3 + 0 = 6', + 'computed2: 6', + ]); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute3: 6 + 3 + 0 = 9', + 'value: 9', + ]); + }); + + test('transaction from autorun', () => { + const log = new Log(); + + const observable1 = new ObservableValue(0, 'MyObservableValue1'); + const observable2 = new ObservableValue(0, 'MyObservableValue2'); + + const computed = derivedObservable('computed', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + autorun((reader) => { + log.log(`value: ${computed.read(reader)}`); + transaction(tx => { + + }); + + }, 'autorun'); + + + }); + + test('from event', () => { + const log = new Log(); + + let value = 0; + const eventEmitter = new Emitter(); + + let id = 0; + const observable = observableFromEvent( + (handler) => { + const curId = id++; + log.log(`subscribed handler ${curId}`); + const disposable = eventEmitter.event(handler); + + return { + dispose: () => { + log.log(`unsubscribed handler ${curId}`); + disposable.dispose(); + }, + }; + }, + () => { + log.log(`compute value ${value}`); + return value; + } + ); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 0', + 'get value: 0', + ]); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 0', + 'get value: 0', + ]); + + const shouldReadObservable = new ObservableValue( + true, + 'shouldReadObservable' + ); + + const autorunDisposable = autorun((reader) => { + if (shouldReadObservable.read(reader)) { + observable.read(reader); + log.log( + `autorun, should read: true, value: ${observable.read(reader)}` + ); + } else { + log.log(`autorun, should read: false`); + } + }, 'MyAutorun'); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'subscribed handler 0', + 'compute value 0', + 'autorun, should read: true, value: 0', + ]); + + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['get value: 0']); + + value = 1; + eventEmitter.fire(); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 1', + 'autorun, should read: true, value: 1', + ]); + + shouldReadObservable.set(false, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'autorun, should read: false', + 'unsubscribed handler 0', + ]); + + shouldReadObservable.set(true, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'subscribed handler 1', + 'compute value 1', + 'autorun, should read: true, value: 1', + ]); + + autorunDisposable.dispose(); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'unsubscribed handler 1', + ]); + }); + + test('get without observers', () => { + // Maybe this scenario should not be supported. + + const log = new Log(); + const observable1 = new ObservableValue(0, 'MyObservableValue1'); + const computed1 = derivedObservable('computed', (reader) => { + const value1 = observable1.read(reader); + const result = value1 % 3; + log.log(`recompute1: ${value1} % 3 = ${result}`); + return result; + }); + const computed2 = derivedObservable('computed', (reader) => { + const value1 = computed1.read(reader); + + const result = value1 * 2; + log.log(`recompute2: ${value1} * 2 = ${result}`); + return result; + }); + const computed3 = derivedObservable('computed', (reader) => { + const value1 = computed1.read(reader); + + const result = value1 * 3; + log.log(`recompute3: ${value1} * 3 = ${result}`); + return result; + }); + const computedSum = derivedObservable('computed', (reader) => { + const value1 = computed2.read(reader); + const value2 = computed3.read(reader); + + const result = value1 + value2; + log.log(`recompute4: ${value1} + ${value2} = ${result}`); + return result; + }); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + log.log(`value: ${computedSum.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 % 3 = 1', + 'recompute2: 1 * 2 = 2', + 'recompute3: 1 * 3 = 3', + 'recompute4: 2 + 3 = 5', + 'value: 5', + ]); + + log.log(`value: ${computedSum.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 % 3 = 1', + 'recompute2: 1 * 2 = 2', + 'recompute3: 1 * 3 = 3', + 'recompute4: 2 + 3 = 5', + 'value: 5', + ]); + }); +}); + +suite('observable details', () => { + test('1', () => { + const log = new Log(); + + class TrackedObservableValue extends BaseObservable { + private value: T; + + constructor(initialValue: T) { + super(); + this.value = initialValue; + } + + public override addObserver(observer: IObserver): void { + log.log(`observable.addObserver ${observer.toString()}`); + super.addObserver(observer); + } + + public override removeObserver(observer: IObserver): void { + log.log(`observable.removeObserver ${observer.toString()}`); + super.removeObserver(observer); + } + + public get(): T { + log.log('observable.get'); + return this.value; + } + + public set(value: T, tx: ITransaction): void { + log.log(`observable.set (value ${value})`); + + if (this.value === value) { + return; + } + this.value = value; + for (const observer of this.observers) { + tx.updateObserver(observer, this); + observer.handleChange(this, undefined); + } + } + } + + const shouldReadObservable = observableValue('shouldReadObservable', true); + const observable = new TrackedObservableValue(0); + const computed = derivedObservable('test', reader => { + if (shouldReadObservable.read(reader)) { + return observable.read(reader) * 2; + } + return 1; + }); + autorun2('test', reader => { + const value = computed.read(reader); + log.log(`autorun: ${value}`); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'observable.addObserver LazyDerived', + 'observable.get', + 'autorun: 0', + ]); + + transaction(tx => { + observable.set(1, tx); + assert.deepStrictEqual(log.getAndClearEntries(), (["observable.set (value 1)"])); + + shouldReadObservable.set(false, tx); + assert.deepStrictEqual(log.getAndClearEntries(), ([])); + + computed.get(); + assert.deepStrictEqual(log.getAndClearEntries(), (["observable.removeObserver LazyDerived"])); + }); + assert.deepStrictEqual(log.getAndClearEntries(), (["autorun: 1"])); + }); +}); + +class Log { + private readonly entries: string[] = []; + public log(message: string): void { + this.entries.push(message); + } + + public getAndClearEntries(): string[] { + const entries = [...this.entries]; + this.entries.length = 0; + return entries; + } +} -- cgit v1.2.3 From 2a447e5e3aef839b96b6301329bd67085232ff1f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Sun, 3 Jul 2022 19:46:52 +0200 Subject: Improves 3wm observables. --- .../mergeEditor/browser/model/mergeEditorModel.ts | 14 +- .../mergeEditor/browser/model/modifiedBaseRange.ts | 4 + .../mergeEditor/browser/model/textModelDiffs.ts | 2 + .../workbench/contrib/mergeEditor/browser/utils.ts | 2 +- .../mergeEditor/browser/view/editorGutter.ts | 29 +-- .../browser/view/editors/codeEditorView.ts | 15 +- .../browser/view/editors/inputCodeEditorView.ts | 267 +++++++++++---------- .../browser/view/editors/resultCodeEditorView.ts | 4 +- .../contrib/mergeEditor/browser/view/viewModel.ts | 1 + 9 files changed, 183 insertions(+), 155 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts index d9f61d89baf..2c1f3b1c4e7 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts @@ -44,7 +44,7 @@ export class MergeEditorModel extends EditorModel { return MergeEditorModelState.upToDate; }); - public readonly isUpToDate = derivedObservable('isUpdating', reader => this.state.read(reader) === MergeEditorModelState.upToDate); + public readonly isUpToDate = derivedObservable('isUpToDate', reader => this.state.read(reader) === MergeEditorModelState.upToDate); public readonly onInitialized = waitForState(this.state, state => state === MergeEditorModelState.upToDate); @@ -84,7 +84,7 @@ export class MergeEditorModel extends EditorModel { return map.size - handledCount; }); - public readonly hasUnhandledConflicts = this.unhandledConflictsCount.map(value => value > 0); + public readonly hasUnhandledConflicts = this.unhandledConflictsCount.map(value => /** @description hasUnhandledConflicts */ value > 0); public readonly input1ResultMapping = derivedObservable('input1ResultMapping', reader => { const resultDiffs = this.resultDiffs.read(reader); @@ -145,7 +145,7 @@ export class MergeEditorModel extends EditorModel { let shouldResetHandlingState = true; this._register( autorunHandleChanges( - 'Recompute State', + 'Merge Editor Model: Recompute State', { handleChange: (ctx) => { if (ctx.didChange(this.modifiedBaseRangeHandlingStateStores)) { @@ -165,6 +165,7 @@ export class MergeEditorModel extends EditorModel { const resultDiffs = this.resultTextModelDiffs.diffs.read(reader); const stores = this.modifiedBaseRangeStateStores.read(reader); transaction(tx => { + /** @description Merge Editor Model: Recompute State */ this.recomputeState(resultDiffs, stores, tx); if (shouldResetHandlingState) { shouldResetHandlingState = false; @@ -202,12 +203,16 @@ export class MergeEditorModel extends EditorModel { ); for (const row of baseRangeWithStoreAndTouchingDiffs) { - row.left[1].set(this.computeState(row.left[0], row.rights), tx); + const newState = this.computeState(row.left[0], row.rights); + if (!row.left[1].get().equals(newState)) { + row.left[1].set(newState, tx); + } } } public resetUnknown(): void { transaction(tx => { + /** @description Reset Unknown Base Range States */ for (const range of this.modifiedBaseRanges.get()) { if (this.getState(range).get().conflicting) { this.setState(range, ModifiedBaseRangeState.default, false, tx); @@ -218,6 +223,7 @@ export class MergeEditorModel extends EditorModel { public mergeNonConflictingDiffs(): void { transaction((tx) => { + /** @description Merge None Conflicting Diffs */ for (const m of this.modifiedBaseRanges.get()) { if (m.isConflicting) { continue; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts index 4dadbb6816a..028b9afe986 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts @@ -293,6 +293,10 @@ export class ModifiedBaseRangeState { } return arr.join(','); } + + equals(newState: ModifiedBaseRangeState): boolean { + return this.input1 === newState.input1 && this.input2 === newState.input2 && this.input2First === newState.input2First; + } } export const enum InputState { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts index c57c0ff51ea..ed894cc7e25 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts @@ -54,6 +54,7 @@ export class TextModelDiffs extends Disposable { } transaction(tx => { + /** @description Starting Diff Computation. */ this._state.set( initializing ? TextModelDiffState.initializing : TextModelDiffState.updating, tx, @@ -72,6 +73,7 @@ export class TextModelDiffs extends Disposable { } transaction(tx => { + /** @description Completed Diff Computation */ if (result.diffs) { this._state.set(TextModelDiffState.upToDate, tx, TextModelDiffChangeReason.textChange); this._diffs.set(result.diffs, tx, TextModelDiffChangeReason.textChange); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts index 23277aeae95..3188a3b2e7a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts @@ -79,7 +79,7 @@ export function applyObservableDecorations(editor: CodeEditorWidget, decorations editor.changeDecorations(a => { decorationIds = a.deltaDecorations(decorationIds, d); }); - }, 'Update Decorations')); + }, `Apply decorations from ${decorations.debugName}`)); d.add({ dispose: () => { editor.changeDecorations(a => { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts index 08a13900476..0a54f686ca4 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts @@ -5,21 +5,24 @@ import { h } from 'vs/base/browser/dom'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { observableSignalFromEvent } from 'vs/base/common/observable'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable'; +import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; export class EditorGutter extends Disposable { private readonly scrollTop = observableFromEvent( this._editor.onDidScrollChange, - (e) => this._editor.getScrollTop() + (e) => /** @description editor.onDidScrollChange */ this._editor.getScrollTop() ); + private readonly isScrollTopZero = this.scrollTop.map((scrollTop) => /** @description isScrollTopZero */ scrollTop === 0); private readonly modelAttached = observableFromEvent( this._editor.onDidChangeModel, - (e) => this._editor.hasModel() + (e) => /** @description editor.onDidChangeModel */ this._editor.hasModel() ); - private readonly changeCounter = new ObservableValue(0, 'counter'); + private readonly editorOnDidChangeViewZones = observableSignalFromEvent('onDidChangeViewZones', this._editor.onDidChangeViewZones); + private readonly editorOnDidContentSizeChange = observableSignalFromEvent('onDidContentSizeChange', this._editor.onDidContentSizeChange); constructor( private readonly _editor: CodeEditorWidget, @@ -34,19 +37,10 @@ export class EditorGutter extends D ); this._register(autorun((reader) => { - scrollDecoration.className = this.scrollTop.read(reader) === 0 ? '' : 'scroll-decoration'; + scrollDecoration.className = this.isScrollTopZero.read(reader) ? '' : 'scroll-decoration'; }, 'update scroll decoration')); - - this._register(autorun((reader) => this.render(reader), 'Render')); - - this._editor.onDidChangeViewZones(e => { - this.changeCounter.set(this.changeCounter.get() + 1, undefined); - }); - - this._editor.onDidContentSizeChange(e => { - this.changeCounter.set(this.changeCounter.get() + 1, undefined); - }); + this._register(autorun((reader) => this.render(reader), 'EditorGutter.Render')); } private readonly views = new Map(); @@ -55,7 +49,10 @@ export class EditorGutter extends D if (!this.modelAttached.read(reader)) { return; } - this.changeCounter.read(reader); + + this.editorOnDidChangeViewZones.read(reader); + this.editorOnDidContentSizeChange.read(reader); + const scrollTop = this.scrollTop.read(reader); const visibleRanges = this._editor.getVisibleRanges(); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index e9a1b888ce0..415eea716ce 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -14,14 +14,14 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModel } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DEFAULT_EDITOR_MAX_DIMENSIONS, DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; -import { IObservable, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable'; +import { IObservable, observableFromEvent, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { setStyle } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel'; export abstract class CodeEditorView extends Disposable { private readonly _viewModel = new ObservableValue(undefined, 'viewModel'); readonly viewModel: IObservable = this._viewModel; - readonly model = this._viewModel.map(m => m?.model); + readonly model = this._viewModel.map(m => /** @description model */ m?.model); protected readonly htmlElements = h('div.code-view', [ h('div.title', { $: 'title' }), @@ -71,15 +71,15 @@ export abstract class CodeEditorView extends Disposable { public readonly isFocused = observableFromEvent( Event.any(this.editor.onDidBlurEditorWidget, this.editor.onDidFocusEditorWidget), - () => this.editor.hasWidgetFocus() + () => /** @description editor.hasWidgetFocus */ this.editor.hasWidgetFocus() ); public readonly cursorPosition = observableFromEvent( this.editor.onDidChangeCursorPosition, - () => this.editor.getPosition() + () => /** @description editor.getPosition */ this.editor.getPosition() ); - public readonly cursorLineNumber = this.cursorPosition.map(p => p?.lineNumber); + public readonly cursorLineNumber = this.cursorPosition.map(p => /** @description cursorPosition.lineNumber */ p?.lineNumber); constructor( @IInstantiationService @@ -103,6 +103,9 @@ export abstract class CodeEditorView extends Disposable { this._title.setLabel(title, description); this._detail.setLabel('', detail); - this._viewModel.set(viewModel, undefined); + transaction(tx => { + /** @description CodeEditorView: Set Model */ + this._viewModel.set(viewModel, tx); + }); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index aadd274e6e1..476e5143570 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -8,6 +8,7 @@ import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable } from 'vs/base/common/lifecycle'; +import { derived, ISettableObservable } from 'vs/base/common/observable'; import { noBreakWhitespace } from 'vs/base/common/strings'; import { isDefined } from 'vs/base/common/types'; import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; @@ -26,7 +27,7 @@ import { EditorGutter, IGutterItemInfo, IGutterItemView } from '../editorGutter' import { CodeEditorView } from './codeEditorView'; export class InputCodeEditorView extends CodeEditorView { - private readonly decorations = derivedObservable('decorations', reader => { + private readonly decorations = derivedObservable(`input${this.inputNumber}.decorations`, reader => { const viewModel = this.viewModel.read(reader); if (!viewModel) { return []; @@ -99,6 +100,136 @@ export class InputCodeEditorView extends CodeEditorView { return result; }); + private readonly modifiedBaseRangeGutterItemInfos = derived(`input${this.inputNumber}.modifiedBaseRangeGutterItemInfos`, reader => { + const viewModel = this.viewModel.read(reader); + if (!viewModel) { return []; } + const model = viewModel.model; + const inputNumber = this.inputNumber; + + return model.modifiedBaseRanges.read(reader) + .filter((r) => r.getInputDiffs(this.inputNumber).length > 0) + .map((baseRange, idx) => ({ + id: idx.toString(), + range: baseRange.getInputRange(this.inputNumber), + enabled: model.isUpToDate, + toggleState: derivedObservable('checkbox is checked', (reader) => { + const input = model + .getState(baseRange) + .read(reader) + .getInput(this.inputNumber); + return input === InputState.second && !baseRange.isOrderRelevant + ? InputState.first + : input; + } + ), + setState: (value, tx) => viewModel.setState( + baseRange, + model + .getState(baseRange) + .get() + .withInputValue(this.inputNumber, value), + tx + ), + toggleBothSides() { + transaction(tx => { + /** @description Context Menu: toggle both sides */ + const state = model + .getState(baseRange) + .get(); + model.setState( + baseRange, + state + .toggle(inputNumber) + .toggle(inputNumber === 1 ? 2 : 1), + true, + tx + ); + }); + }, + getContextMenuActions: () => { + const state = model.getState(baseRange).get(); + const handled = model.isHandled(baseRange).get(); + + const update = (newState: ModifiedBaseRangeState) => { + transaction(tx => { + /** @description Context Menu: Update Base Range State */ + return viewModel.setState(baseRange, newState, tx); + }); + }; + + function action(id: string, label: string, targetState: ModifiedBaseRangeState, checked: boolean) { + const action = new Action(id, label, undefined, true, () => { + update(targetState); + }); + action.checked = checked; + return action; + } + const both = state.input1 && state.input2; + + return [ + baseRange.input1Diffs.length > 0 + ? action( + 'mergeEditor.acceptInput1', + localize('mergeEditor.accept', 'Accept {0}', model.input1Title), + state.toggle(1), + state.input1 + ) + : undefined, + baseRange.input2Diffs.length > 0 + ? action( + 'mergeEditor.acceptInput2', + localize('mergeEditor.accept', 'Accept {0}', model.input2Title), + state.toggle(2), + state.input2 + ) + : undefined, + baseRange.isConflicting + ? setFields( + action( + 'mergeEditor.acceptBoth', + localize( + 'mergeEditor.acceptBoth', + 'Accept Both' + ), + state.withInput1(!both).withInput2(!both), + both + ), + { enabled: baseRange.canBeCombined } + ) + : undefined, + new Separator(), + baseRange.isConflicting + ? setFields( + action( + 'mergeEditor.swap', + localize('mergeEditor.swap', 'Swap'), + state.swap(), + false + ), + { enabled: !state.isEmpty && (!both || baseRange.isOrderRelevant) } + ) + : undefined, + + setFields( + new Action( + 'mergeEditor.markAsHandled', + localize('mergeEditor.markAsHandled', 'Mark as Handled'), + undefined, + true, + () => { + transaction((tx) => { + /** @description Context Menu: Mark as handled */ + model.setHandled(baseRange, !handled, tx); + }); + } + ), + { checked: handled } + ), + ].filter(isDefined); + } + })); + }); + constructor( public readonly inputNumber: 1 | 2, @IInstantiationService instantiationService: IInstantiationService, @@ -112,127 +243,7 @@ export class InputCodeEditorView extends CodeEditorView { this._register( new EditorGutter(this.editor, this.htmlElements.gutterDiv, { getIntersectingGutterItems: (range, reader) => { - const viewModel = this.viewModel.read(reader); - if (!viewModel) { return []; } - const model = viewModel.model; - - return model.modifiedBaseRanges.read(reader) - .filter((r) => r.getInputDiffs(this.inputNumber).length > 0) - .map((baseRange, idx) => ({ - id: idx.toString(), - range: baseRange.getInputRange(this.inputNumber), - enabled: model.isUpToDate, - toggleState: derivedObservable('toggle', (reader) => { - const input = model - .getState(baseRange) - .read(reader) - .getInput(this.inputNumber); - return input === InputState.second && !baseRange.isOrderRelevant - ? InputState.first - : input; - } - ), - setState: (value, tx) => viewModel.setState( - baseRange, - model - .getState(baseRange) - .get() - .withInputValue(this.inputNumber, value), - tx - ), - toggleBothSides() { - transaction(tx => { - const state = model - .getState(baseRange) - .get(); - model.setState( - baseRange, - state - .toggle(inputNumber) - .toggle(inputNumber === 1 ? 2 : 1), - true, - tx - ); - }); - }, - getContextMenuActions: () => { - const state = model.getState(baseRange).get(); - const handled = model.isHandled(baseRange).get(); - - const update = (newState: ModifiedBaseRangeState) => { - transaction(tx => viewModel.setState(baseRange, newState, tx)); - }; - - function action(id: string, label: string, targetState: ModifiedBaseRangeState, checked: boolean) { - const action = new Action(id, label, undefined, true, () => { - update(targetState); - }); - action.checked = checked; - return action; - } - const both = state.input1 && state.input2; - - return [ - baseRange.input1Diffs.length > 0 - ? action( - 'mergeEditor.acceptInput1', - localize('mergeEditor.accept', 'Accept {0}', model.input1Title), - state.toggle(1), - state.input1 - ) - : undefined, - baseRange.input2Diffs.length > 0 - ? action( - 'mergeEditor.acceptInput2', - localize('mergeEditor.accept', 'Accept {0}', model.input2Title), - state.toggle(2), - state.input2 - ) - : undefined, - baseRange.isConflicting - ? setFields( - action( - 'mergeEditor.acceptBoth', - localize( - 'mergeEditor.acceptBoth', - 'Accept Both' - ), - state.withInput1(!both).withInput2(!both), - both - ), - { enabled: baseRange.canBeCombined } - ) - : undefined, - new Separator(), - baseRange.isConflicting - ? setFields( - action( - 'mergeEditor.swap', - localize('mergeEditor.swap', 'Swap'), - state.swap(), - false - ), - { enabled: !state.isEmpty && (!both || baseRange.isOrderRelevant) } - ) - : undefined, - - setFields( - new Action( - 'mergeEditor.markAsHandled', - localize('mergeEditor.markAsHandled', 'Mark as Handled'), - undefined, - true, - () => { - transaction((tx) => { - model.setHandled(baseRange, !handled, tx); - }); - } - ), - { checked: handled } - ), - ].filter(isDefined); - } - })); + return this.modifiedBaseRangeGutterItemInfos.read(reader); }, createView: (item, target) => new MergeConflictGutterItemView(item, target, contextMenuService, themeService), }) @@ -253,7 +264,7 @@ export interface ModifiedBaseRangeGutterItemInfo extends IGutterItemInfo { } export class MergeConflictGutterItemView extends Disposable implements IGutterItemView { - private readonly item = new ObservableValue(undefined, 'item'); + private readonly item: ISettableObservable; constructor( item: ModifiedBaseRangeGutterItemInfo, @@ -263,7 +274,7 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt ) { super(); - this.item.set(item, undefined); + this.item = new ObservableValue(item, 'item'); target.classList.add('merge-accept-gutter-marker'); @@ -315,11 +326,12 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt } else { checkBox.enable(); } - }, 'Update Toggle State') + }, 'Update Checkbox') ); this._register(checkBox.onChange(() => { transaction(tx => { + /** @description Handle Checkbox Change */ this.item.get()!.setState(checkBox.checked, tx); }); })); @@ -337,6 +349,9 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt } update(baseRange: ModifiedBaseRangeGutterItemInfo): void { - this.item.set(baseRange, undefined); + transaction(tx => { + /** @description MergeConflictGutterItemView: Updating new base range */ + this.item.set(baseRange, tx); + }); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts index fc6919af08a..89e8490cf90 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts @@ -14,7 +14,7 @@ import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverV import { CodeEditorView } from './codeEditorView'; export class ResultCodeEditorView extends CodeEditorView { - private readonly decorations = derivedObservable('decorations', reader => { + private readonly decorations = derivedObservable('result.decorations', reader => { const viewModel = this.viewModel.read(reader); if (!viewModel) { return []; @@ -125,6 +125,6 @@ export class ResultCodeEditorView extends CodeEditorView { '{0} Remaining Conflicts', count )); - }, 'update label')); + }, 'update remainingConflicts label')); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts index 3f90643ea4a..46c954fb2c3 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts @@ -135,6 +135,7 @@ export class MergeEditorViewModel { return; } transaction(tx => { + /** @description Toggle Active Conflict */ this.setState( activeModifiedBaseRange, this.model.getState(activeModifiedBaseRange).get().toggle(inputNumber), -- cgit v1.2.3 From 3ab72751707134b7a6ec523d6183629f6cfa52ad Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 4 Jul 2022 07:35:17 +0200 Subject: SCM - Fix issue with commit dropdown button (#154034) Fix issue with commit dropdown button --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 1a733fb7680..47801f55fd9 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2411,7 +2411,12 @@ export class SCMViewPane extends ViewPane { return; } else if (isSCMActionButton(e.element)) { this.scmViewService.focus(e.element.repository); - this.actionButtonRenderer.focusActionButton(e.element); + + // Focus the action button + const target = e.browserEvent?.target as HTMLElement; + if (target.classList.contains('monaco-tl-row') || target.classList.contains('button-container')) { + this.actionButtonRenderer.focusActionButton(e.element); + } return; } -- cgit v1.2.3 From 2471ff08d8d42e30abe2d606ae56c13e639a1d6e Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 4 Jul 2022 10:51:33 +0200 Subject: Removes old observable test. --- .../audioCues/test/browser/observable.test.ts | 468 --------------------- 1 file changed, 468 deletions(-) delete mode 100644 src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts b/src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts deleted file mode 100644 index 646464cb636..00000000000 --- a/src/vs/workbench/contrib/audioCues/test/browser/observable.test.ts +++ /dev/null @@ -1,468 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { Emitter } from 'vs/base/common/event'; -import { autorun, autorun2, BaseObservable, derivedObservable, IObserver, ITransaction, observableFromEvent, observableValue, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; - -suite('observable integration', () => { - test('basic observable + autorun', () => { - const log = new Log(); - const observable = new ObservableValue(0, 'MyObservableValue'); - - autorun((reader) => { - log.log(`value: ${observable.read(reader)}`); - }, 'MyAutorun'); - assert.deepStrictEqual(log.getAndClearEntries(), ['value: 0']); - - observable.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), ['value: 1']); - - observable.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - transaction((tx) => { - observable.set(2, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - observable.set(3, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - }); - - assert.deepStrictEqual(log.getAndClearEntries(), ['value: 3']); - }); - - test('basic computed + autorun', () => { - const log = new Log(); - const observable1 = new ObservableValue(0, 'MyObservableValue1'); - const observable2 = new ObservableValue(0, 'MyObservableValue2'); - - const computed = derivedObservable('computed', (reader) => { - const value1 = observable1.read(reader); - const value2 = observable2.read(reader); - const sum = value1 + value2; - log.log(`recompute: ${value1} + ${value2} = ${sum}`); - return sum; - }); - - autorun((reader) => { - log.log(`value: ${computed.read(reader)}`); - }, 'MyAutorun'); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 0 + 0 = 0', - 'value: 0', - ]); - - observable1.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 1 + 0 = 1', - 'value: 1', - ]); - - observable2.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 1 + 1 = 2', - 'value: 2', - ]); - - transaction((tx) => { - observable1.set(5, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - observable2.set(5, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - }); - - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 5 + 5 = 10', - 'value: 10', - ]); - - transaction((tx) => { - observable1.set(6, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - observable2.set(4, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - }); - - assert.deepStrictEqual(log.getAndClearEntries(), ['recompute: 6 + 4 = 10']); - }); - - test('read during transaction', () => { - const log = new Log(); - const observable1 = new ObservableValue(0, 'MyObservableValue1'); - const observable2 = new ObservableValue(0, 'MyObservableValue2'); - - const computed = derivedObservable('computed', (reader) => { - const value1 = observable1.read(reader); - const value2 = observable2.read(reader); - const sum = value1 + value2; - log.log(`recompute: ${value1} + ${value2} = ${sum}`); - return sum; - }); - - autorun((reader) => { - log.log(`value: ${computed.read(reader)}`); - }, 'MyAutorun'); - - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 0 + 0 = 0', - 'value: 0', - ]); - - log.log(`computed is ${computed.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), ['computed is 0']); - - transaction((tx) => { - observable1.set(-1, tx); - log.log(`computed is ${computed.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: -1 + 0 = -1', - 'computed is -1', - ]); - - log.log(`computed is ${computed.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), ['computed is -1']); - - observable2.set(1, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - }); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: -1 + 1 = 0', - 'value: 0', - ]); - }); - - test('topological order', () => { - const log = new Log(); - const observable1 = new ObservableValue(0, 'MyObservableValue1'); - const observable2 = new ObservableValue(0, 'MyObservableValue2'); - - const computed1 = derivedObservable('computed1', (reader) => { - const value1 = observable1.read(reader); - const value2 = observable2.read(reader); - const sum = value1 + value2; - log.log(`recompute1: ${value1} + ${value2} = ${sum}`); - return sum; - }); - - const computed2 = derivedObservable('computed2', (reader) => { - const value1 = computed1.read(reader); - const value2 = observable1.read(reader); - const value3 = observable2.read(reader); - const sum = value1 + value2 + value3; - log.log(`recompute2: ${value1} + ${value2} + ${value3} = ${sum}`); - return sum; - }); - - const computed3 = derivedObservable('computed3', (reader) => { - const value1 = computed2.read(reader); - const value2 = observable1.read(reader); - const value3 = observable2.read(reader); - const sum = value1 + value2 + value3; - log.log(`recompute3: ${value1} + ${value2} + ${value3} = ${sum}`); - return sum; - }); - - autorun((reader) => { - log.log(`value: ${computed3.read(reader)}`); - }, 'MyAutorun'); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 0 + 0 = 0', - 'recompute2: 0 + 0 + 0 = 0', - 'recompute3: 0 + 0 + 0 = 0', - 'value: 0', - ]); - - observable1.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 1 + 0 = 1', - 'recompute2: 1 + 1 + 0 = 2', - 'recompute3: 2 + 1 + 0 = 3', - 'value: 3', - ]); - - transaction((tx) => { - observable1.set(2, tx); - log.log(`computed2: ${computed2.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 2 + 0 = 2', - 'recompute2: 2 + 2 + 0 = 4', - 'computed2: 4', - ]); - - observable1.set(3, tx); - log.log(`computed2: ${computed2.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 3 + 0 = 3', - 'recompute2: 3 + 3 + 0 = 6', - 'computed2: 6', - ]); - }); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute3: 6 + 3 + 0 = 9', - 'value: 9', - ]); - }); - - test('transaction from autorun', () => { - const log = new Log(); - - const observable1 = new ObservableValue(0, 'MyObservableValue1'); - const observable2 = new ObservableValue(0, 'MyObservableValue2'); - - const computed = derivedObservable('computed', (reader) => { - const value1 = observable1.read(reader); - const value2 = observable2.read(reader); - const sum = value1 + value2; - log.log(`recompute: ${value1} + ${value2} = ${sum}`); - return sum; - }); - - autorun((reader) => { - log.log(`value: ${computed.read(reader)}`); - transaction(tx => { - - }); - - }, 'autorun'); - - - }); - - test('from event', () => { - const log = new Log(); - - let value = 0; - const eventEmitter = new Emitter(); - - let id = 0; - const observable = observableFromEvent( - (handler) => { - const curId = id++; - log.log(`subscribed handler ${curId}`); - const disposable = eventEmitter.event(handler); - - return { - dispose: () => { - log.log(`unsubscribed handler ${curId}`); - disposable.dispose(); - }, - }; - }, - () => { - log.log(`compute value ${value}`); - return value; - } - ); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - log.log(`get value: ${observable.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'compute value 0', - 'get value: 0', - ]); - - log.log(`get value: ${observable.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'compute value 0', - 'get value: 0', - ]); - - const shouldReadObservable = new ObservableValue( - true, - 'shouldReadObservable' - ); - - const autorunDisposable = autorun((reader) => { - if (shouldReadObservable.read(reader)) { - observable.read(reader); - log.log( - `autorun, should read: true, value: ${observable.read(reader)}` - ); - } else { - log.log(`autorun, should read: false`); - } - }, 'MyAutorun'); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'subscribed handler 0', - 'compute value 0', - 'autorun, should read: true, value: 0', - ]); - - log.log(`get value: ${observable.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), ['get value: 0']); - - value = 1; - eventEmitter.fire(); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'compute value 1', - 'autorun, should read: true, value: 1', - ]); - - shouldReadObservable.set(false, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'autorun, should read: false', - 'unsubscribed handler 0', - ]); - - shouldReadObservable.set(true, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'subscribed handler 1', - 'compute value 1', - 'autorun, should read: true, value: 1', - ]); - - autorunDisposable.dispose(); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'unsubscribed handler 1', - ]); - }); - - test('get without observers', () => { - // Maybe this scenario should not be supported. - - const log = new Log(); - const observable1 = new ObservableValue(0, 'MyObservableValue1'); - const computed1 = derivedObservable('computed', (reader) => { - const value1 = observable1.read(reader); - const result = value1 % 3; - log.log(`recompute1: ${value1} % 3 = ${result}`); - return result; - }); - const computed2 = derivedObservable('computed', (reader) => { - const value1 = computed1.read(reader); - - const result = value1 * 2; - log.log(`recompute2: ${value1} * 2 = ${result}`); - return result; - }); - const computed3 = derivedObservable('computed', (reader) => { - const value1 = computed1.read(reader); - - const result = value1 * 3; - log.log(`recompute3: ${value1} * 3 = ${result}`); - return result; - }); - const computedSum = derivedObservable('computed', (reader) => { - const value1 = computed2.read(reader); - const value2 = computed3.read(reader); - - const result = value1 + value2; - log.log(`recompute4: ${value1} + ${value2} = ${result}`); - return result; - }); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - observable1.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - log.log(`value: ${computedSum.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 1 % 3 = 1', - 'recompute2: 1 * 2 = 2', - 'recompute3: 1 * 3 = 3', - 'recompute4: 2 + 3 = 5', - 'value: 5', - ]); - - log.log(`value: ${computedSum.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 1 % 3 = 1', - 'recompute2: 1 * 2 = 2', - 'recompute3: 1 * 3 = 3', - 'recompute4: 2 + 3 = 5', - 'value: 5', - ]); - }); -}); - -suite('observable details', () => { - test('1', () => { - const log = new Log(); - - class TrackedObservableValue extends BaseObservable { - private value: T; - - constructor(initialValue: T) { - super(); - this.value = initialValue; - } - - public override addObserver(observer: IObserver): void { - log.log(`observable.addObserver ${observer.toString()}`); - super.addObserver(observer); - } - - public override removeObserver(observer: IObserver): void { - log.log(`observable.removeObserver ${observer.toString()}`); - super.removeObserver(observer); - } - - public get(): T { - log.log('observable.get'); - return this.value; - } - - public set(value: T, tx: ITransaction): void { - log.log(`observable.set (value ${value})`); - - if (this.value === value) { - return; - } - this.value = value; - for (const observer of this.observers) { - tx.updateObserver(observer, this); - observer.handleChange(this, undefined); - } - } - } - - const shouldReadObservable = observableValue('shouldReadObservable', true); - const observable = new TrackedObservableValue(0); - const computed = derivedObservable('test', reader => { - if (shouldReadObservable.read(reader)) { - return observable.read(reader) * 2; - } - return 1; - }); - autorun2('test', reader => { - const value = computed.read(reader); - log.log(`autorun: ${value}`); - }); - - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'observable.addObserver LazyDerived', - 'observable.get', - 'autorun: 0', - ]); - - transaction(tx => { - observable.set(1, tx); - assert.deepStrictEqual(log.getAndClearEntries(), (["observable.set (value 1)"])); - - shouldReadObservable.set(false, tx); - assert.deepStrictEqual(log.getAndClearEntries(), ([])); - - computed.get(); - assert.deepStrictEqual(log.getAndClearEntries(), (["observable.removeObserver LazyDerived"])); - }); - assert.deepStrictEqual(log.getAndClearEntries(), (["autorun: 1"])); - }); -}); - -class Log { - private readonly entries: string[] = []; - public log(message: string): void { - this.entries.push(message); - } - - public getAndClearEntries(): string[] { - const entries = [...this.entries]; - this.entries.length = 0; - return entries; - } -} -- cgit v1.2.3 From a1a0283b7a018c97caa524fd3045e7ce92f27ee0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 4 Jul 2022 14:21:51 +0200 Subject: adopt #153865 (#154067) --- src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 6faf5d3c10a..31cc538fa56 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -158,7 +158,12 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio constructor() { super({ id: 'workbench.extensions.installLocalExtensions', - get title() { return localize('select and install local extensions', "Install Local Extensions in '{0}'...", server.label); }, + get title() { + return { + value: localize('select and install local extensions', "Install Local Extensions in '{0}'...", server.label), + original: `Install Local Extensions in '${server.label}'...`, + }; + }, category: localize({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"), icon: installLocalInRemoteIcon, f1: true, -- cgit v1.2.3 From 496d4b2c1f62db770a832847ac64251d09d506d3 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 4 Jul 2022 14:24:56 +0200 Subject: enable import/export profiles actions in no profile mode (#154022) - enable actions - bring back old code to import profile --- .../common/userDataProfileActions.ts | 32 +++++++++++++++++----- 1 file changed, 25 insertions(+), 7 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts index e796f1f897e..cda5ca4d8fe 100644 --- a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts +++ b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts @@ -7,8 +7,8 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; -import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; -import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFileService } from 'vs/platform/files/common/files'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -19,6 +19,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { CATEGORIES } from 'vs/workbench/common/actions'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; registerAction2(class CreateFromCurrentProfileAction extends Action2 { constructor() { @@ -196,14 +197,14 @@ registerAction2(class ExportProfileAction extends Action2 { original: 'Export Settings Profile...' }, category: PROFILES_CATEGORY, - f1: true, - precondition: PROFILES_ENABLEMENT_CONTEXT, menu: [ { id: ManageProfilesSubMenu, group: '3_import_export_profiles', when: PROFILES_ENABLEMENT_CONTEXT, order: 1 + }, { + id: MenuId.CommandPalette } ] }); @@ -241,14 +242,14 @@ registerAction2(class ImportProfileAction extends Action2 { original: 'Import Settings Profile...' }, category: PROFILES_CATEGORY, - f1: true, - precondition: PROFILES_ENABLEMENT_CONTEXT, menu: [ { id: ManageProfilesSubMenu, group: '3_import_export_profiles', when: PROFILES_ENABLEMENT_CONTEXT, order: 2 + }, { + id: MenuId.CommandPalette } ] }); @@ -260,6 +261,19 @@ registerAction2(class ImportProfileAction extends Action2 { const fileService = accessor.get(IFileService); const requestService = accessor.get(IRequestService); const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService); + const dialogService = accessor.get(IDialogService); + const contextKeyService = accessor.get(IContextKeyService); + + const isSettingProfilesEnabled = contextKeyService.contextMatchesRules(PROFILES_ENABLEMENT_CONTEXT); + + if (!isSettingProfilesEnabled) { + if (!(await dialogService.confirm({ + title: localize('import profile title', "Import Settings from a Profile"), + message: localize('confiirmation message', "This will replace your current settings. Are you sure you want to continue?"), + })).confirmed) { + return; + } + } const disposables = new DisposableStore(); const quickPick = disposables.add(quickInputService.createQuickPick()); @@ -278,7 +292,11 @@ registerAction2(class ImportProfileAction extends Action2 { quickPick.hide(); const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService); if (profile) { - await userDataProfileImportExportService.importProfile(profile); + if (isSettingProfilesEnabled) { + await userDataProfileImportExportService.importProfile(profile); + } else { + await userDataProfileImportExportService.setProfile(profile); + } } })); disposables.add(quickPick.onDidHide(() => disposables.dispose())); -- cgit v1.2.3 From 61188536ed77f5515346560db34f4046ab646d45 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 4 Jul 2022 14:32:58 +0200 Subject: debt - add `Event#fromObservable` to bridge between observables and emitters (#154069) --- src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 137f5a9b992..660d10cb487 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -17,12 +17,12 @@ import { IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/edit import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; -import { autorun } from 'vs/workbench/contrib/audioCues/browser/observable'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { assertType } from 'vs/base/common/types'; +import { Event } from 'vs/base/common/event'; export class MergeEditorInputData { constructor( @@ -123,10 +123,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements this._store.add(input2); this._store.add(result); - this._store.add(autorun(reader => { - this._model?.hasUnhandledConflicts.read(reader); - this._onDidChangeDirty.fire(undefined); - }, 'drive::onDidChangeDirty')); + this._store.add(Event.fromObservable(this._model.hasUnhandledConflicts)(() => this._onDidChangeDirty.fire(undefined))); } this._ignoreUnhandledConflictsForDirtyState = undefined; -- cgit v1.2.3 From 62ab77fa2127d17624f918745029b80206c1bfd6 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 4 Jul 2022 14:46:33 +0200 Subject: use `ICommandActionTitle` over simple string (#154081) https://github.com/microsoft/vscode/issues/153865 --- .../contrib/inlayHints/browser/inlayHintsAccessibilty.ts | 10 ++++++++-- .../languageStatus/browser/languageStatus.contribution.ts | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts index 2766516fdc0..e3d83192f51 100644 --- a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts +++ b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts @@ -174,7 +174,10 @@ registerAction2(class StartReadHints extends EditorAction2 { constructor() { super({ id: 'inlayHints.startReadingLineWithHint', - title: localize('read.title', 'Read Line With Inline Hints'), + title: { + value: localize('read.title', 'Read Line With Inline Hints'), + original: 'Read Line With Inline Hints' + }, precondition: EditorContextKeys.hasInlayHintsProvider, f1: true }); @@ -193,7 +196,10 @@ registerAction2(class StopReadHints extends EditorAction2 { constructor() { super({ id: 'inlayHints.stopReadingLineWithHint', - title: localize('stop.title', 'Stop Inlay Hints Reading'), + title: { + value: localize('stop.title', 'Stop Inlay Hints Reading'), + original: 'Stop Inlay Hints Reading' + }, precondition: InlayHintsAccessibility.IsReading, f1: true, keybinding: { diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts index 661059e89b9..63b120d3b3c 100644 --- a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts +++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts @@ -398,8 +398,14 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'editor.inlayHints.Reset', - title: localize('reset', 'Reset Language Status Interaction Counter'), - category: localize('cat', 'View'), + title: { + value: localize('reset', 'Reset Language Status Interaction Counter'), + original: 'Reset Language Status Interaction Counter' + }, + category: { + value: localize('cat', 'View'), + original: 'View' + }, f1: true }); } -- cgit v1.2.3 From cdc1920447dc5d857ec947981f79a21674dcaa74 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 4 Jul 2022 14:53:00 +0200 Subject: introduce immediate update (#154082) --- .../contrib/output/browser/outputServices.ts | 2 +- .../contrib/output/common/outputChannelModel.ts | 28 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts index c13ec6b5e0c..e9f97c01b29 100644 --- a/src/vs/workbench/contrib/output/browser/outputServices.ts +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -48,7 +48,7 @@ class OutputChannel extends Disposable implements IOutputChannel { } update(mode: OutputChannelUpdateMode, till?: number): void { - this.model.update(mode, till); + this.model.update(mode, till, true); } clear(): void { diff --git a/src/vs/workbench/contrib/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts index 4c01e67797c..58f8374650b 100644 --- a/src/vs/workbench/contrib/output/common/outputChannelModel.ts +++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts @@ -26,7 +26,7 @@ import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/out export interface IOutputChannelModel extends IDisposable { readonly onDispose: Event; append(output: string): void; - update(mode: OutputChannelUpdateMode, till?: number): void; + update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void; loadModel(): Promise; clear(): void; replace(value: string): void; @@ -129,12 +129,12 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel } clear(): void { - this.update(OutputChannelUpdateMode.Clear, this.endOffset); + this.update(OutputChannelUpdateMode.Clear, this.endOffset, true); } - update(mode: OutputChannelUpdateMode, till?: number): void { + update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void { const loadModelPromise: Promise = this.loadModelPromise ? this.loadModelPromise : Promise.resolve(); - loadModelPromise.then(() => this.doUpdate(mode, till)); + loadModelPromise.then(() => this.doUpdate(mode, till, immediate)); } loadModel(): Promise { @@ -174,7 +174,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel return this.model; } - private doUpdate(mode: OutputChannelUpdateMode, till?: number): void { + private doUpdate(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void { if (mode === OutputChannelUpdateMode.Clear || mode === OutputChannelUpdateMode.Replace) { this.startOffset = this.endOffset = isNumber(till) ? till : this.endOffset; this.cancelModelUpdate(); @@ -198,7 +198,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel } else { - this.appendContent(this.model, token); + this.appendContent(this.model, immediate, token); } } @@ -206,7 +206,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel this.doUpdateModel(model, [EditOperation.delete(model.getFullModelRange())], VSBuffer.fromString('')); } - private async appendContent(model: ITextModel, token: CancellationToken): Promise { + private async appendContent(model: ITextModel, immediate: boolean, token: CancellationToken): Promise { this.appendThrottler.trigger(async () => { /* Abort if operation is cancelled */ if (token.isCancellationRequested) { @@ -234,7 +234,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel const lastLineMaxColumn = model.getLineMaxColumn(lastLine); const edits = [EditOperation.insert(new Position(lastLine, lastLineMaxColumn), contentToAppend.toString())]; this.doUpdateModel(model, edits, contentToAppend); - }); + }, immediate ? 0 : undefined); } private async replaceContent(model: ITextModel, token: CancellationToken): Promise { @@ -298,10 +298,10 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel if (!this.modelUpdateInProgress) { if (isNumber(size) && this.endOffset > size) { // Reset - Content is removed - this.update(OutputChannelUpdateMode.Clear, 0); + this.update(OutputChannelUpdateMode.Clear, 0, true); } } - this.update(OutputChannelUpdateMode.Append); + this.update(OutputChannelUpdateMode.Append, undefined, false /* Not needed to update immediately. Wait to collect more changes and update. */); } } @@ -340,13 +340,13 @@ class OutputChannelBackedByFile extends FileOutputChannelModel implements IOutpu override append(message: string): void { this.write(message); - this.update(OutputChannelUpdateMode.Append); + this.update(OutputChannelUpdateMode.Append, undefined, this.isVisible()); } override replace(message: string): void { const till = this._offset; this.write(message); - this.update(OutputChannelUpdateMode.Replace, till); + this.update(OutputChannelUpdateMode.Replace, till, true); } private write(content: string): void { @@ -391,8 +391,8 @@ export class DelegatedOutputChannelModel extends Disposable implements IOutputCh this.outputChannelModel.then(outputChannelModel => outputChannelModel.append(output)); } - update(mode: OutputChannelUpdateMode, till?: number): void { - this.outputChannelModel.then(outputChannelModel => outputChannelModel.update(mode, till)); + update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void { + this.outputChannelModel.then(outputChannelModel => outputChannelModel.update(mode, till, immediate)); } loadModel(): Promise { -- cgit v1.2.3 From 428c8e29bd6b18729e4ba3c6af971f8db9eb8694 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 4 Jul 2022 15:37:48 +0200 Subject: SCM - More focus related fixes to the commit action button (#154087) More focus related fixes to the commit action button --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 47801f55fd9..41cce292993 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2400,6 +2400,7 @@ export class SCMViewPane extends ViewPane { if (widget) { widget.focus(); + this.tree.setFocus([], e.browserEvent); const selection = this.tree.getSelection(); @@ -2416,6 +2417,7 @@ export class SCMViewPane extends ViewPane { const target = e.browserEvent?.target as HTMLElement; if (target.classList.contains('monaco-tl-row') || target.classList.contains('button-container')) { this.actionButtonRenderer.focusActionButton(e.element); + this.tree.setFocus([], e.browserEvent); } return; -- cgit v1.2.3 From 11d625ee1ff47bb46121af63ef6a0bb4f96cd9bd Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 4 Jul 2022 16:35:58 +0200 Subject: completes observable refactoring (#154089) --- .../browser/audioCueDebuggerContribution.ts | 2 +- .../browser/audioCueLineFeatureContribution.ts | 26 +++++--------- .../contrib/audioCues/browser/audioCueService.ts | 4 +-- .../contrib/audioCues/browser/observable.ts | 40 ---------------------- .../mergeEditor/browser/model/mergeEditorModel.ts | 24 ++++++------- .../mergeEditor/browser/model/textModelDiffs.ts | 6 ++-- .../workbench/contrib/mergeEditor/browser/utils.ts | 9 +++-- .../mergeEditor/browser/view/editorGutter.ts | 9 +++-- .../browser/view/editors/codeEditorView.ts | 4 +-- .../browser/view/editors/inputCodeEditorView.ts | 13 ++++--- .../browser/view/editors/resultCodeEditorView.ts | 8 ++--- .../mergeEditor/browser/view/mergeEditor.ts | 2 +- .../contrib/mergeEditor/browser/view/viewModel.ts | 8 ++--- .../contrib/mergeEditor/test/browser/model.test.ts | 2 +- 14 files changed, 52 insertions(+), 105 deletions(-) delete mode 100644 src/vs/workbench/contrib/audioCues/browser/observable.ts (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts index fc3727f804d..3b400d3fafc 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { autorunWithStore } from 'vs/base/common/observable'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; -import { autorunWithStore } from 'vs/workbench/contrib/audioCues/browser/observable'; import { IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; export class AudioCueLineDebuggerContribution diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts index edbdd468675..490fab6c4c6 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts @@ -12,21 +12,11 @@ import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/edito import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { - autorun, - autorunDelta, - constObservable, - derivedObservable, - observableFromEvent, - observableFromPromise, - IObservable, - wasEventTriggeredRecently, - debouncedObservable, -} from 'vs/workbench/contrib/audioCues/browser/observable'; import { ITextModel } from 'vs/editor/common/model'; import { GhostTextController } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextController'; import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; +import { autorun, autorunDelta, constObservable, debouncedObservable, derived, IObservable, observableFromEvent, observableFromPromise, wasEventTriggeredRecently } from 'vs/base/common/observable'; export class AudioCueLineFeatureContribution extends Disposable @@ -48,7 +38,7 @@ export class AudioCueLineFeatureContribution ) { super(); - const someAudioCueFeatureIsEnabled = derivedObservable( + const someAudioCueFeatureIsEnabled = derived( 'someAudioCueFeatureIsEnabled', (reader) => this.features.some((feature) => @@ -73,7 +63,7 @@ export class AudioCueLineFeatureContribution ); this._register( - autorun((reader) => { + autorun('updateAudioCuesEnabled', (reader) => { this.store.clear(); if (!someAudioCueFeatureIsEnabled.read(reader)) { @@ -84,7 +74,7 @@ export class AudioCueLineFeatureContribution if (activeEditor) { this.registerAudioCuesForEditor(activeEditor.editor, activeEditor.model, this.store); } - }, 'updateAudioCuesEnabled') + }) ); } @@ -118,7 +108,7 @@ export class AudioCueLineFeatureContribution const featureStates = this.features.map((feature) => { const lineFeatureState = feature.getObservableState(editor, editorModel); - const isFeaturePresent = derivedObservable( + const isFeaturePresent = derived( `isPresentInLine:${feature.audioCue.name}`, (reader) => { if (!this.audioCueService.isEnabled(feature.audioCue).read(reader)) { @@ -130,7 +120,7 @@ export class AudioCueLineFeatureContribution : lineFeatureState.read(reader).isPresent(lineNumber); } ); - return derivedObservable( + return derived( `typingDebouncedFeatureState:\n${feature.audioCue.name}`, (reader) => feature.debounceWhileTyping && isTyping.read(reader) @@ -139,7 +129,7 @@ export class AudioCueLineFeatureContribution ); }); - const state = derivedObservable( + const state = derived( 'states', (reader) => ({ lineNumber: debouncedLineNumber.read(reader), @@ -282,7 +272,7 @@ class InlineCompletionLineFeature implements LineFeature { : undefined )); - return derivedObservable('ghostText', reader => { + return derived('ghostText', reader => { const ghostText = activeGhostText.read(reader)?.read(reader); return { isPresent(lineNumber) { diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts index f313933a8ec..4e491840f5a 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts @@ -9,9 +9,9 @@ import { FileAccess } from 'vs/base/common/network'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { observableFromEvent, IObservable, derivedObservable } from 'vs/workbench/contrib/audioCues/browser/observable'; import { Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; +import { IObservable, observableFromEvent, derived } from 'vs/base/common/observable'; export const IAudioCueService = createDecorator('audioCue'); @@ -95,7 +95,7 @@ export class AudioCueService extends Disposable implements IAudioCueService { ), () => this.configurationService.getValue<'on' | 'off' | 'auto'>(cue.settingsKey) ); - return derivedObservable('audio cue enabled', reader => { + return derived('audio cue enabled', reader => { const setting = settingObservable.read(reader); if ( setting === 'on' || diff --git a/src/vs/workbench/contrib/audioCues/browser/observable.ts b/src/vs/workbench/contrib/audioCues/browser/observable.ts deleted file mode 100644 index 37d2f8a97c8..00000000000 --- a/src/vs/workbench/contrib/audioCues/browser/observable.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IDisposable } from 'vs/base/common/lifecycle'; -import * as observable from 'vs/base/common/observable'; -export { - observableFromEvent, - autorunWithStore, - IObservable, - transaction, - ITransaction, - autorunDelta, - constObservable, - observableFromPromise, - wasEventTriggeredRecently, - debouncedObservable, - autorunHandleChanges, - waitForState, - keepAlive, - IReader, - derivedObservableWithCache, - derivedObservableWithWritableCache, -} from 'vs/base/common/observable'; -import * as observableValue from 'vs/base/common/observableImpl/base'; - -export function autorun(fn: (reader: observable.IReader) => void, name: string): IDisposable { - return observable.autorun(name, fn); -} - -export class ObservableValue extends observableValue.ObservableValue { - constructor(initialValue: T, name: string) { - super(name, initialValue); - } -} - -export function derivedObservable(name: string, computeFn: (reader: observable.IReader) => T): observable.IObservable { - return observable.derived(name, computeFn); -} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts index 721cd75e3d1..6bf21e30d44 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts @@ -5,11 +5,11 @@ import { CompareResult, equals } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; +import { ISettableObservable, derived, waitForState, observableValue, keepAlive, autorunHandleChanges, transaction, IReader, ITransaction, IObservable } from 'vs/base/common/observable'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel, ITextSnapshot } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; -import { autorunHandleChanges, derivedObservable, IObservable, IReader, ITransaction, keepAlive, ObservableValue, transaction, waitForState } from 'vs/workbench/contrib/audioCues/browser/observable'; import { IDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { DetailedLineRangeMapping, DocumentMapping, LineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; @@ -28,7 +28,7 @@ export class MergeEditorModel extends EditorModel { private readonly input2TextModelDiffs = this._register(new TextModelDiffs(this.base, this.input2, this.diffComputer)); private readonly resultTextModelDiffs = this._register(new TextModelDiffs(this.base, this.result, this.diffComputer)); - public readonly state = derivedObservable('state', reader => { + public readonly state = derived('state', reader => { const states = [ this.input1TextModelDiffs, this.input2TextModelDiffs, @@ -44,11 +44,11 @@ export class MergeEditorModel extends EditorModel { return MergeEditorModelState.upToDate; }); - public readonly isUpToDate = derivedObservable('isUpToDate', reader => this.state.read(reader) === MergeEditorModelState.upToDate); + public readonly isUpToDate = derived('isUpToDate', reader => this.state.read(reader) === MergeEditorModelState.upToDate); public readonly onInitialized = waitForState(this.state, state => state === MergeEditorModelState.upToDate); - public readonly modifiedBaseRanges = derivedObservable('modifiedBaseRanges', (reader) => { + public readonly modifiedBaseRanges = derived('modifiedBaseRanges', (reader) => { const input1Diffs = this.input1TextModelDiffs.diffs.read(reader); const input2Diffs = this.input2TextModelDiffs.diffs.read(reader); @@ -60,22 +60,22 @@ export class MergeEditorModel extends EditorModel { public readonly resultDiffs = this.resultTextModelDiffs.diffs; private readonly modifiedBaseRangeStateStores = - derivedObservable('modifiedBaseRangeStateStores', reader => { + derived('modifiedBaseRangeStateStores', reader => { const map = new Map( - this.modifiedBaseRanges.read(reader).map(s => ([s, new ObservableValue(ModifiedBaseRangeState.default, 'State')])) + this.modifiedBaseRanges.read(reader).map(s => ([s, observableValue('State', ModifiedBaseRangeState.default)])) ); return map; }); private readonly modifiedBaseRangeHandlingStateStores = - derivedObservable('modifiedBaseRangeHandlingStateStores', reader => { + derived('modifiedBaseRangeHandlingStateStores', reader => { const map = new Map( - this.modifiedBaseRanges.read(reader).map(s => ([s, new ObservableValue(false, 'State')])) + this.modifiedBaseRanges.read(reader).map(s => ([s, observableValue('State', false)])) ); return map; }); - public readonly unhandledConflictsCount = derivedObservable('unhandledConflictsCount', reader => { + public readonly unhandledConflictsCount = derived('unhandledConflictsCount', reader => { const map = this.modifiedBaseRangeHandlingStateStores.read(reader); let handledCount = 0; for (const [_key, value] of map) { @@ -86,7 +86,7 @@ export class MergeEditorModel extends EditorModel { public readonly hasUnhandledConflicts = this.unhandledConflictsCount.map(value => /** @description hasUnhandledConflicts */ value > 0); - public readonly input1ResultMapping = derivedObservable('input1ResultMapping', reader => { + public readonly input1ResultMapping = derived('input1ResultMapping', reader => { const resultDiffs = this.resultDiffs.read(reader); const modifiedBaseRanges = DocumentMapping.betweenOutputs(this.input1LinesDiffs.read(reader), resultDiffs, this.input1.getLineCount()); @@ -103,7 +103,7 @@ export class MergeEditorModel extends EditorModel { ); }); - public readonly input2ResultMapping = derivedObservable('input2ResultMapping', reader => { + public readonly input2ResultMapping = derived('input2ResultMapping', reader => { const resultDiffs = this.resultDiffs.read(reader); const modifiedBaseRanges = DocumentMapping.betweenOutputs(this.input2LinesDiffs.read(reader), resultDiffs, this.input2.getLineCount()); @@ -192,7 +192,7 @@ export class MergeEditorModel extends EditorModel { return this.resultTextModelDiffs.getResultRange(baseRange, reader); } - private recomputeState(resultDiffs: DetailedLineRangeMapping[], stores: Map>, tx: ITransaction): void { + private recomputeState(resultDiffs: DetailedLineRangeMapping[], stores: Map>, tx: ITransaction): void { const baseRangeWithStoreAndTouchingDiffs = leftJoin( stores, resultDiffs, diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts index ed894cc7e25..af21adbebb5 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts @@ -7,17 +7,17 @@ import { compareBy, numberComparator } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { ITextModel } from 'vs/editor/common/model'; -import { IObservable, IReader, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { DetailedLineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { LineRangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { IDiffComputer } from './diffComputer'; +import { IObservable, IReader, ITransaction, observableValue, transaction } from 'vs/base/common/observable'; export class TextModelDiffs extends Disposable { private updateCount = 0; - private readonly _state = new ObservableValue(TextModelDiffState.initializing, 'LiveDiffState'); - private readonly _diffs = new ObservableValue([], 'LiveDiffs'); + private readonly _state = observableValue('LiveDiffState', TextModelDiffState.initializing); + private readonly _diffs = observableValue('LiveDiffs', []); private readonly barrier = new ReentrancyBarrier(); private isDisposed = false; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts index 3188a3b2e7a..7e92f970387 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts @@ -5,11 +5,10 @@ import { CompareResult, ArrayQueue } from 'vs/base/common/arrays'; import { BugIndicatingError } from 'vs/base/common/errors'; -import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IObservable, autorun } from 'vs/base/common/observable'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; -import { IObservable, autorun } from 'vs/workbench/contrib/audioCues/browser/observable'; -import { IDisposable } from 'xterm'; export class ReentrancyBarrier { private isActive = false; @@ -74,12 +73,12 @@ function toSize(value: number | string): string { export function applyObservableDecorations(editor: CodeEditorWidget, decorations: IObservable): IDisposable { const d = new DisposableStore(); let decorationIds: string[] = []; - d.add(autorun(reader => { + d.add(autorun(`Apply decorations from ${decorations.debugName}`, reader => { const d = decorations.read(reader); editor.changeDecorations(a => { decorationIds = a.deltaDecorations(decorationIds, d); }); - }, `Apply decorations from ${decorations.debugName}`)); + })); d.add({ dispose: () => { editor.changeDecorations(a => { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts index 0a54f686ca4..7289cc61637 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts @@ -5,9 +5,8 @@ import { h } from 'vs/base/browser/dom'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { observableSignalFromEvent } from 'vs/base/common/observable'; +import { autorun, IReader, observableFromEvent, observableSignalFromEvent } from 'vs/base/common/observable'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; export class EditorGutter extends Disposable { @@ -36,11 +35,11 @@ export class EditorGutter extends D .root ); - this._register(autorun((reader) => { + this._register(autorun('update scroll decoration', (reader) => { scrollDecoration.className = this.isScrollTopZero.read(reader) ? '' : 'scroll-decoration'; - }, 'update scroll decoration')); + })); - this._register(autorun((reader) => this.render(reader), 'EditorGutter.Render')); + this._register(autorun('EditorGutter.Render', (reader) => this.render(reader))); } private readonly views = new Map(); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index f843553c33b..f6a409656f8 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -8,18 +8,18 @@ import { IView, IViewSize } from 'vs/base/browser/ui/grid/grid'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { IObservable, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable'; import { IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextModel } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DEFAULT_EDITOR_MAX_DIMENSIONS, DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; -import { IObservable, observableFromEvent, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { setStyle } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel'; export abstract class CodeEditorView extends Disposable { - private readonly _viewModel = new ObservableValue(undefined, 'viewModel'); + private readonly _viewModel = observableValue('viewModel', undefined); readonly viewModel: IObservable = this._viewModel; readonly model = this._viewModel.map(m => /** @description model */ m?.model); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts index 476e5143570..abadef681ba 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts @@ -8,7 +8,7 @@ import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Action, IAction, Separator } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { Disposable } from 'vs/base/common/lifecycle'; -import { derived, ISettableObservable } from 'vs/base/common/observable'; +import { autorun, derived, IObservable, ISettableObservable, ITransaction, transaction, observableValue } from 'vs/base/common/observable'; import { noBreakWhitespace } from 'vs/base/common/strings'; import { isDefined } from 'vs/base/common/types'; import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; @@ -19,7 +19,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { attachToggleStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { autorun, derivedObservable, IObservable, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { InputState, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange'; import { applyObservableDecorations, setFields } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors'; @@ -27,7 +26,7 @@ import { EditorGutter, IGutterItemInfo, IGutterItemView } from '../editorGutter' import { CodeEditorView } from './codeEditorView'; export class InputCodeEditorView extends CodeEditorView { - private readonly decorations = derivedObservable(`input${this.inputNumber}.decorations`, reader => { + private readonly decorations = derived(`input${this.inputNumber}.decorations`, reader => { const viewModel = this.viewModel.read(reader); if (!viewModel) { return []; @@ -112,7 +111,7 @@ export class InputCodeEditorView extends CodeEditorView { id: idx.toString(), range: baseRange.getInputRange(this.inputNumber), enabled: model.isUpToDate, - toggleState: derivedObservable('checkbox is checked', (reader) => { + toggleState: derived('checkbox is checked', (reader) => { const input = model .getState(baseRange) .read(reader) @@ -274,7 +273,7 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt ) { super(); - this.item = new ObservableValue(item, 'item'); + this.item = observableValue('item', item); target.classList.add('merge-accept-gutter-marker'); @@ -309,7 +308,7 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt checkBox.domNode.classList.add('accept-conflict-group'); this._register( - autorun((reader) => { + autorun('Update Checkbox', (reader) => { const item = this.item.read(reader)!; const value = item.toggleState.read(reader); const iconMap: Record = { @@ -326,7 +325,7 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt } else { checkBox.enable(); } - }, 'Update Checkbox') + }) ); this._register(checkBox.onChange(() => { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts index 0efc1d88247..e1557eb0c9a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { CompareResult } from 'vs/base/common/arrays'; +import { autorun, derived } from 'vs/base/common/observable'; import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { autorun, derivedObservable } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { applyObservableDecorations, join } from 'vs/workbench/contrib/mergeEditor/browser/utils'; import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors'; import { CodeEditorView } from './codeEditorView'; export class ResultCodeEditorView extends CodeEditorView { - private readonly decorations = derivedObservable('result.decorations', reader => { + private readonly decorations = derived('result.decorations', reader => { const viewModel = this.viewModel.read(reader); if (!viewModel) { return []; @@ -107,7 +107,7 @@ export class ResultCodeEditorView extends CodeEditorView { this._register(applyObservableDecorations(this.editor, this.decorations)); - this._register(autorun(reader => { + this._register(autorun('update remainingConflicts label', reader => { const model = this.model.read(reader); if (!model) { return; @@ -126,6 +126,6 @@ export class ResultCodeEditorView extends CodeEditorView { count ); - }, 'update remainingConflicts label')); + })); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 74629320cd2..544e07332cb 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -12,6 +12,7 @@ import { Color } from 'vs/base/common/color'; import { BugIndicatingError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { autorunWithStore, IObservable } from 'vs/base/common/observable'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/mergeEditor'; @@ -37,7 +38,6 @@ import { AbstractTextEditor } from 'vs/workbench/browser/parts/editor/textEditor import { EditorInputWithOptions, EditorResourceAccessor, IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions'; -import { autorunWithStore, IObservable } from 'vs/workbench/contrib/audioCues/browser/observable'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { DocumentMapping, getOppositeDirection, MappingDirection } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts index 46c954fb2c3..de910cceaee 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { findLast } from 'vs/base/common/arrays'; +import { derived, derivedObservableWithWritableCache, IReader, ITransaction, observableValue, transaction } from 'vs/base/common/observable'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { derivedObservable, derivedObservableWithWritableCache, IReader, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; import { ModifiedBaseRange, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange'; @@ -25,9 +25,9 @@ export class MergeEditorViewModel { return editors.find((e) => e.isFocused.read(reader)) || lastValue; }); - private readonly manuallySetActiveModifiedBaseRange = new ObservableValue< + private readonly manuallySetActiveModifiedBaseRange = observableValue< ModifiedBaseRange | undefined - >(undefined, 'manuallySetActiveModifiedBaseRange'); + >('manuallySetActiveModifiedBaseRange', undefined); private getRange(editor: CodeEditorView, modifiedBaseRange: ModifiedBaseRange, reader: IReader | undefined): LineRange { if (editor === this.resultCodeEditorView) { @@ -38,7 +38,7 @@ export class MergeEditorViewModel { } } - public readonly activeModifiedBaseRange = derivedObservable( + public readonly activeModifiedBaseRange = derived( 'activeModifiedBaseRange', (reader) => { const focusedEditor = this.lastFocusedEditor.read(reader); diff --git a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts index 9963829694f..ea1a9794545 100644 --- a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts +++ b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts @@ -5,13 +5,13 @@ import assert = require('assert'); import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { transaction } from 'vs/base/common/observable'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; import { createModelServices, createTextModel } from 'vs/editor/test/common/testTextModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { transaction } from 'vs/workbench/contrib/audioCues/browser/observable'; import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; -- cgit v1.2.3 From ce7ced73adcd0ca8bdc2b957c56423e5a13666c0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 5 Jul 2022 09:15:30 +0200 Subject: Fix #153663 (#154104) --- .../extensions/browser/extensionsActions.ts | 76 +++++++++++----------- 1 file changed, 38 insertions(+), 38 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 710e811a4f7..c6d34dc012f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1623,38 +1623,38 @@ export class SetColorThemeAction extends ExtensionAction { private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; private static readonly DisabledClass = `${SetColorThemeAction.EnabledClass} disabled`; - private colorThemes: IWorkbenchColorTheme[] = []; - constructor( @IExtensionService extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, @IQuickInputService private readonly quickInputService: IQuickInputService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, ) { super(SetColorThemeAction.ID, SetColorThemeAction.TITLE.value, SetColorThemeAction.DisabledClass, false); this._register(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidColorThemeChange)(() => this.update(), this)); - workbenchThemeService.getColorThemes().then(colorThemes => { - this.colorThemes = colorThemes; - this.update(); - }); this.update(); } update(): void { - this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.colorThemes.some(th => isThemeFromExtension(th, this.extension)); - this.class = this.enabled ? SetColorThemeAction.EnabledClass : SetColorThemeAction.DisabledClass; + this.workbenchThemeService.getColorThemes().then(colorThemes => { + this.enabled = this.computeEnablement(colorThemes); + this.class = this.enabled ? SetColorThemeAction.EnabledClass : SetColorThemeAction.DisabledClass; + }); + } + + private computeEnablement(colorThemes: IWorkbenchColorTheme[]): boolean { + return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && colorThemes.some(th => isThemeFromExtension(th, this.extension)); } override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { - this.colorThemes = await this.workbenchThemeService.getColorThemes(); + const colorThemes = await this.workbenchThemeService.getColorThemes(); - this.update(); - if (!this.enabled) { + if (!this.computeEnablement(colorThemes)) { return; } const currentTheme = this.workbenchThemeService.getColorTheme(); const delayer = new Delayer(100); - const picks = getQuickPickEntries(this.colorThemes, currentTheme, this.extension, showCurrentTheme); + const picks = getQuickPickEntries(colorThemes, currentTheme, this.extension, showCurrentTheme); const pickedTheme = await this.quickInputService.pick( picks, { @@ -1674,37 +1674,37 @@ export class SetFileIconThemeAction extends ExtensionAction { private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; private static readonly DisabledClass = `${SetFileIconThemeAction.EnabledClass} disabled`; - private fileIconThemes: IWorkbenchFileIconTheme[] = []; - constructor( @IExtensionService extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, - @IQuickInputService private readonly quickInputService: IQuickInputService + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, ) { super(SetFileIconThemeAction.ID, SetFileIconThemeAction.TITLE.value, SetFileIconThemeAction.DisabledClass, false); this._register(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidFileIconThemeChange)(() => this.update(), this)); - workbenchThemeService.getFileIconThemes().then(fileIconThemes => { - this.fileIconThemes = fileIconThemes; - this.update(); - }); this.update(); } update(): void { - this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.fileIconThemes.some(th => isThemeFromExtension(th, this.extension)); - this.class = this.enabled ? SetFileIconThemeAction.EnabledClass : SetFileIconThemeAction.DisabledClass; + this.workbenchThemeService.getFileIconThemes().then(fileIconThemes => { + this.enabled = this.computeEnablement(fileIconThemes); + this.class = this.enabled ? SetFileIconThemeAction.EnabledClass : SetFileIconThemeAction.DisabledClass; + }); + } + + private computeEnablement(colorThemfileIconThemess: IWorkbenchFileIconTheme[]): boolean { + return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && colorThemfileIconThemess.some(th => isThemeFromExtension(th, this.extension)); } override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { - this.fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); - this.update(); - if (!this.enabled) { + const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); + if (!this.computeEnablement(fileIconThemes)) { return; } const currentTheme = this.workbenchThemeService.getFileIconTheme(); const delayer = new Delayer(100); - const picks = getQuickPickEntries(this.fileIconThemes, currentTheme, this.extension, showCurrentTheme); + const picks = getQuickPickEntries(fileIconThemes, currentTheme, this.extension, showCurrentTheme); const pickedTheme = await this.quickInputService.pick( picks, { @@ -1724,38 +1724,38 @@ export class SetProductIconThemeAction extends ExtensionAction { private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; private static readonly DisabledClass = `${SetProductIconThemeAction.EnabledClass} disabled`; - private productIconThemes: IWorkbenchProductIconTheme[] = []; - constructor( @IExtensionService extensionService: IExtensionService, @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, - @IQuickInputService private readonly quickInputService: IQuickInputService + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, ) { super(SetProductIconThemeAction.ID, SetProductIconThemeAction.TITLE.value, SetProductIconThemeAction.DisabledClass, false); this._register(Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidProductIconThemeChange)(() => this.update(), this)); - workbenchThemeService.getProductIconThemes().then(productIconThemes => { - this.productIconThemes = productIconThemes; - this.update(); - }); this.update(); } update(): void { - this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.productIconThemes.some(th => isThemeFromExtension(th, this.extension)); - this.class = this.enabled ? SetProductIconThemeAction.EnabledClass : SetProductIconThemeAction.DisabledClass; + this.workbenchThemeService.getProductIconThemes().then(productIconThemes => { + this.enabled = this.computeEnablement(productIconThemes); + this.class = this.enabled ? SetProductIconThemeAction.EnabledClass : SetProductIconThemeAction.DisabledClass; + }); + } + + private computeEnablement(productIconThemes: IWorkbenchProductIconTheme[]): boolean { + return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && productIconThemes.some(th => isThemeFromExtension(th, this.extension)); } override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { - this.productIconThemes = await this.workbenchThemeService.getProductIconThemes(); - this.update(); - if (!this.enabled) { + const productIconThemes = await this.workbenchThemeService.getProductIconThemes(); + if (!this.computeEnablement(productIconThemes)) { return; } const currentTheme = this.workbenchThemeService.getProductIconTheme(); const delayer = new Delayer(100); - const picks = getQuickPickEntries(this.productIconThemes, currentTheme, this.extension, showCurrentTheme); + const picks = getQuickPickEntries(productIconThemes, currentTheme, this.extension, showCurrentTheme); const pickedTheme = await this.quickInputService.pick( picks, { -- cgit v1.2.3 From 866f22e2d17f5da33803b4f41dc681f5a5a3ef5f Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 5 Jul 2022 09:16:36 +0200 Subject: Adresses #153865 (#154100) --- .../mergeEditor/browser/commands/commands.ts | 110 +++++++++++++++------ .../mergeEditor/browser/commands/devCommands.ts | 16 ++- 2 files changed, 93 insertions(+), 33 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 375c202ea6a..7e100501342 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -6,6 +6,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; +import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; @@ -19,7 +20,7 @@ export class OpenMergeEditor extends Action2 { constructor() { super({ id: '_open.mergeEditor', - title: localize('title', "Open Merge Editor"), + title: { value: localize('title', "Open Merge Editor"), original: 'Open Merge Editor' }, }); } run(accessor: ServicesAccessor, ...args: unknown[]): void { @@ -111,14 +112,19 @@ export class SetMixedLayout extends Action2 { constructor() { super({ id: 'merge.mixedLayout', - title: localize('layout.mixed', "Mixed Layout"), + title: { + value: localize('layout.mixed', 'Mixed Layout'), + original: 'Mixed Layout', + }, toggled: ctxMergeEditorLayout.isEqualTo('mixed'), - menu: [{ - id: MenuId.EditorTitle, - when: ctxIsMergeEditor, - group: '1_merge', - order: 9, - }], + menu: [ + { + id: MenuId.EditorTitle, + when: ctxIsMergeEditor, + group: '1_merge', + order: 9, + }, + ], precondition: ctxIsMergeEditor, }); } @@ -135,7 +141,7 @@ export class SetColumnLayout extends Action2 { constructor() { super({ id: 'merge.columnLayout', - title: localize('layout.column', "Column Layout"), + title: { value: localize('layout.column', "Column Layout"), original: 'Column Layout' }, toggled: ctxMergeEditorLayout.isEqualTo('columns'), menu: [{ id: MenuId.EditorTitle, @@ -155,18 +161,28 @@ export class SetColumnLayout extends Action2 { } } +const mergeEditorCategory: ILocalizedString = { + value: localize('mergeEditor', 'Merge Editor'), + original: 'Merge Editor', +}; + export class GoToNextConflict extends Action2 { constructor() { super({ id: 'merge.goToNextConflict', - category: localize('mergeEditor', "Merge Editor"), - title: localize('merge.goToNextConflict', "Go to Next Conflict"), + category: mergeEditorCategory, + title: { + value: localize('merge.goToNextConflict', 'Go to Next Conflict'), + original: 'Go to Next Conflict', + }, icon: Codicon.arrowDown, - menu: [{ - id: MenuId.EditorTitle, - when: ctxIsMergeEditor, - group: 'navigation', - }], + menu: [ + { + id: MenuId.EditorTitle, + when: ctxIsMergeEditor, + group: 'navigation', + }, + ], f1: true, precondition: ctxIsMergeEditor, }); @@ -184,14 +200,22 @@ export class GoToPreviousConflict extends Action2 { constructor() { super({ id: 'merge.goToPreviousConflict', - category: localize('mergeEditor', "Merge Editor"), - title: localize('merge.goToPreviousConflict', "Go to Previous Conflict"), + category: mergeEditorCategory, + title: { + value: localize( + 'merge.goToPreviousConflict', + 'Go to Previous Conflict' + ), + original: 'Go to Previous Conflict', + }, icon: Codicon.arrowUp, - menu: [{ - id: MenuId.EditorTitle, - when: ctxIsMergeEditor, - group: 'navigation', - }], + menu: [ + { + id: MenuId.EditorTitle, + when: ctxIsMergeEditor, + group: 'navigation', + }, + ], f1: true, precondition: ctxIsMergeEditor, }); @@ -209,8 +233,14 @@ export class ToggleActiveConflictInput1 extends Action2 { constructor() { super({ id: 'merge.toggleActiveConflictInput1', - category: localize('mergeEditor', "Merge Editor"), - title: localize('merge.toggleCurrentConflictFromLeft', "Toggle Current Conflict from Left"), + category: mergeEditorCategory, + title: { + value: localize( + 'merge.toggleCurrentConflictFromLeft', + 'Toggle Current Conflict from Left' + ), + original: 'Toggle Current Conflict from Left', + }, f1: true, precondition: ctxIsMergeEditor, }); @@ -232,8 +262,14 @@ export class ToggleActiveConflictInput2 extends Action2 { constructor() { super({ id: 'merge.toggleActiveConflictInput2', - category: localize('mergeEditor', "Merge Editor"), - title: localize('merge.toggleCurrentConflictFromRight', "Toggle Current Conflict from Right"), + category: mergeEditorCategory, + title: { + value: localize( + 'merge.toggleCurrentConflictFromRight', + 'Toggle Current Conflict from Right' + ), + original: 'Toggle Current Conflict from Right', + }, f1: true, precondition: ctxIsMergeEditor, }); @@ -255,8 +291,14 @@ export class CompareInput1WithBaseCommand extends Action2 { constructor() { super({ id: 'mergeEditor.compareInput1WithBase', - category: localize('mergeEditor', "Merge Editor"), - title: localize('mergeEditor.compareInput1WithBase', "Compare Input 1 With Base"), + category: mergeEditorCategory, + title: { + value: localize( + 'mergeEditor.compareInput1WithBase', + 'Compare Input 1 With Base' + ), + original: 'Compare Input 1 With Base', + }, f1: true, precondition: ctxIsMergeEditor, }); @@ -272,8 +314,14 @@ export class CompareInput2WithBaseCommand extends Action2 { constructor() { super({ id: 'mergeEditor.compareInput2WithBase', - category: localize('mergeEditor', "Merge Editor"), - title: localize('mergeEditor.compareInput2WithBase', "Compare Input 2 With Base"), + category: mergeEditorCategory, + title: { + value: localize( + 'mergeEditor.compareInput2WithBase', + 'Compare Input 2 With Base' + ), + original: 'Compare Input 2 With Base', + }, f1: true, precondition: ctxIsMergeEditor, }); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts index 83a458d15f5..aaf15c771da 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts @@ -34,7 +34,13 @@ export class MergeEditorCopyContentsToJSON extends Action2 { super({ id: 'merge.dev.copyContents', category: 'Merge Editor (Dev)', - title: localize('merge.dev.copyContents', "Copy Contents of Inputs, Base and Result as JSON"), + title: { + value: localize( + 'merge.dev.copyContents', + 'Copy Contents of Inputs, Base and Result as JSON' + ), + original: 'Copy Contents of Inputs, Base and Result as JSON', + }, icon: Codicon.layoutCentered, f1: true, precondition: ctxIsMergeEditor, @@ -80,7 +86,13 @@ export class MergeEditorOpenContents extends Action2 { super({ id: 'merge.dev.openContents', category: 'Merge Editor (Dev)', - title: localize('merge.dev.openContents', "Open Contents of Inputs, Base and Result from JSON"), + title: { + value: localize( + 'merge.dev.openContents', + 'Open Contents of Inputs, Base and Result from JSON' + ), + original: 'Open Contents of Inputs, Base and Result from JSON', + }, icon: Codicon.layoutCentered, f1: true, }); -- cgit v1.2.3 From f4f1b04d872a2b94d9a5105a1eefb81a213c07f2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 5 Jul 2022 09:54:28 +0200 Subject: Add a setting to enable `sandbox: true` for windows (#154062) * sandbox - allow enabled sandbox in a full build * sandbox - reduce `electron-browser` in workbench * sandbox - reduce `electron-browser` in platform * sandbox - add a setting to enable sandbox mode for window * fix lint * Revert "sandbox - reduce `electron-browser` in workbench" This reverts commit 36a5167cf9525e98a37137915f9f8c748ca47ae5. * Revert "sandbox - reduce `electron-browser` in platform" This reverts commit 6f49d704a5403dbf286e6eb30700387454e0d047. * fix layer issue * fix some js errors --- .../contrib/extensions/browser/extensions.contribution.ts | 3 ++- .../contrib/relauncher/browser/relauncher.contribution.ts | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index f64e267746c..b3c51e8a568 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -77,6 +77,7 @@ import { UnsupportedExtensionsMigrationContrib } from 'vs/workbench/contrib/exte import { isWeb } from 'vs/base/common/platform'; import { ExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import product from 'vs/platform/product/common/product'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -228,7 +229,7 @@ Registry.as(ConfigurationExtensions.Configuration) 'extensions.experimental.useUtilityProcess': { type: 'boolean', description: localize('extensionsUseUtilityProcess', "When enabled, the extension host will be launched using the new UtilityProcess Electron API."), - default: false + default: product.quality === 'stable' ? false : true // disabled by default in stable for now }, [WORKSPACE_TRUST_EXTENSION_SUPPORT]: { type: 'object', diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index 148c6c8d379..1680881aeb1 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -26,7 +26,7 @@ interface IConfiguration extends IWindowsConfiguration { debug?: { console?: { wordWrap?: boolean } }; editor?: { accessibilitySupport?: 'on' | 'off' | 'auto' }; security?: { workspace?: { trust?: { enabled?: boolean } } }; - window: IWindowSettings & { experimental?: { windowControlsOverlay?: { enabled?: boolean } } }; + window: IWindowSettings & { experimental?: { windowControlsOverlay?: { enabled?: boolean }; useSandbox?: boolean } }; workbench?: { experimental?: { settingsProfiles?: { enabled?: boolean } } }; } @@ -34,6 +34,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private titleBarStyle: 'native' | 'custom' | undefined; private windowControlsOverlayEnabled: boolean | undefined; + private windowSandboxEnabled: boolean | undefined; private nativeTabs: boolean | undefined; private nativeFullScreen: boolean | undefined; private clickThroughInactive: boolean | undefined; @@ -66,11 +67,16 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo } // Windows: Window Controls Overlay - if (isWindows && typeof config.window?.experimental?.windowControlsOverlay?.enabled === 'boolean' && config.window?.experimental?.windowControlsOverlay?.enabled !== this.windowControlsOverlayEnabled) { + if (isWindows && typeof config.window?.experimental?.windowControlsOverlay?.enabled === 'boolean' && config.window.experimental.windowControlsOverlay.enabled !== this.windowControlsOverlayEnabled) { this.windowControlsOverlayEnabled = config.window.experimental.windowControlsOverlay.enabled; changed = true; } + // Windows: Sandbox + if (typeof config.window?.experimental?.useSandbox === 'boolean' && config.window.experimental.useSandbox !== this.windowSandboxEnabled) { + this.windowSandboxEnabled = config.window.experimental.useSandbox; + changed = true; + } // macOS: Native tabs if (isMacintosh && typeof config.window?.nativeTabs === 'boolean' && config.window.nativeTabs !== this.nativeTabs) { -- cgit v1.2.3 From d508c2c2e439995a557e98d30f4748c6155ac794 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 5 Jul 2022 11:54:31 +0200 Subject: Improves observable name. --- .../workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts index 6bf21e30d44..06a3dcbf827 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts @@ -62,7 +62,7 @@ export class MergeEditorModel extends EditorModel { private readonly modifiedBaseRangeStateStores = derived('modifiedBaseRangeStateStores', reader => { const map = new Map( - this.modifiedBaseRanges.read(reader).map(s => ([s, observableValue('State', ModifiedBaseRangeState.default)])) + this.modifiedBaseRanges.read(reader).map(s => ([s, observableValue(`BaseRangeState${s.baseRange}`, ModifiedBaseRangeState.default)])) ); return map; }); @@ -70,7 +70,7 @@ export class MergeEditorModel extends EditorModel { private readonly modifiedBaseRangeHandlingStateStores = derived('modifiedBaseRangeHandlingStateStores', reader => { const map = new Map( - this.modifiedBaseRanges.read(reader).map(s => ([s, observableValue('State', false)])) + this.modifiedBaseRanges.read(reader).map(s => ([s, observableValue(`BaseRangeHandledState${s.baseRange}`, false)])) ); return map; }); -- cgit v1.2.3 From c86b009309aaca9ab18d977df578949cac16bcec Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 5 Jul 2022 12:48:45 +0200 Subject: Fixes merge editor bug, improves testing infrastructure and adds test. --- .../mergeEditor/browser/commands/commands.ts | 28 +++++ .../mergeEditor/browser/commands/devCommands.ts | 9 +- .../browser/mergeEditor.contribution.ts | 3 +- .../mergeEditor/browser/mergeEditorInput.ts | 3 + .../mergeEditor/browser/model/mergeEditorModel.ts | 9 +- .../mergeEditor/browser/model/modifiedBaseRange.ts | 18 ++- .../contrib/mergeEditor/test/browser/model.test.ts | 132 ++++++++++++++++++--- 7 files changed, 172 insertions(+), 30 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 7e100501342..892febf378e 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -10,6 +10,7 @@ import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { MergeEditorInput, MergeEditorInputData } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; @@ -350,3 +351,30 @@ function mergeEditorCompare(editorService: IEditorService, commandService: IComm function openDiffEditor(commandService: ICommandService, left: URI, right: URI, label?: string) { commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, left, right, label); } + +export class OpenBaseFile extends Action2 { + constructor() { + super({ + id: 'merge.openBaseEditor', + category: mergeEditorCategory, + title: { + value: localize('merge.openBaseEditor', 'Open Base File'), + original: 'Open Base File', + }, + f1: true, + precondition: ctxIsMergeEditor, + }); + } + + run(accessor: ServicesAccessor): void { + const openerService = accessor.get(IOpenerService); + const { activeEditorPane } = accessor.get(IEditorService); + if (activeEditorPane instanceof MergeEditor) { + const vm = activeEditorPane.viewModel.get(); + if (!vm) { + return; + } + openerService.open(vm.model.base.uri); + } + } +} diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts index aaf15c771da..6728dc93b1a 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts @@ -29,7 +29,6 @@ interface MergeEditorContents { } export class MergeEditorCopyContentsToJSON extends Action2 { - constructor() { super({ id: 'merge.dev.copyContents', @@ -81,7 +80,6 @@ export class MergeEditorCopyContentsToJSON extends Action2 { } export class MergeEditorOpenContents extends Action2 { - constructor() { super({ id: 'merge.dev.openContents', @@ -110,11 +108,14 @@ export class MergeEditorOpenContents extends Action2 { prompt: localize('mergeEditor.enterJSON', 'Enter JSON'), value: await clipboardService.readText(), }); - if (!result) { + if (result === undefined) { return; } - const content: MergeEditorContents = JSON.parse(result); + const content: MergeEditorContents = + result !== '' + ? JSON.parse(result) + : { base: '', input1: '', input2: '', result: '', languageId: 'plaintext' }; const scheme = 'merge-editor-dev'; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index 136249c41fa..8dc07e6463f 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -9,7 +9,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; -import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenMergeEditor, ToggleActiveConflictInput1, ToggleActiveConflictInput2, SetColumnLayout, SetMixedLayout } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands'; +import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenMergeEditor, ToggleActiveConflictInput1, ToggleActiveConflictInput2, SetColumnLayout, SetMixedLayout, OpenBaseFile } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands'; import { MergeEditorCopyContentsToJSON, MergeEditorOpenContents } from 'vs/workbench/contrib/mergeEditor/browser/commands/devCommands'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; @@ -34,6 +34,7 @@ Registry.as(EditorExtensions.EditorFactory).registerEdit registerAction2(SetMixedLayout); registerAction2(SetColumnLayout); registerAction2(OpenMergeEditor); +registerAction2(OpenBaseFile); registerAction2(MergeEditorCopyContentsToJSON); registerAction2(MergeEditorOpenContents); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 660d10cb487..053c7309754 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -113,6 +113,9 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements this.input2.description, result.object.textEditorModel, this._instaService.createInstance(EditorWorkerServiceDiffComputer), + { + resetUnknownOnInitialization: true + }, ); await this._model.onInitialized; diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts index 06a3dcbf827..255615448d3 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts @@ -134,6 +134,7 @@ export class MergeEditorModel extends EditorModel { readonly input2Description: string | undefined, readonly result: ITextModel, private readonly diffComputer: IDiffComputer, + options: { resetUnknownOnInitialization: boolean }, @IModelService private readonly modelService: IModelService, @ILanguageService private readonly languageService: ILanguageService, ) { @@ -183,9 +184,11 @@ export class MergeEditorModel extends EditorModel { ) ); - this.onInitialized.then(() => { - this.resetUnknown(); - }); + if (options.resetUnknownOnInitialization) { + this.onInitialized.then(() => { + this.resetUnknown(); + }); + } } public getRangeInResult(baseRange: LineRange, reader?: IReader): LineRange { diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts index 028b9afe986..2e6b6031382 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts @@ -281,21 +281,29 @@ export class ModifiedBaseRangeState { } public toString(): string { - const arr: ('1' | '2')[] = []; + const arr: string[] = []; if (this.input1) { - arr.push('1'); + arr.push('1✓'); } if (this.input2) { - arr.push('2'); + arr.push('2✓'); } if (this.input2First) { arr.reverse(); } + if (this.conflicting) { + arr.push('conflicting'); + } return arr.join(','); } - equals(newState: ModifiedBaseRangeState): boolean { - return this.input1 === newState.input1 && this.input2 === newState.input2 && this.input2First === newState.input2First; + equals(other: ModifiedBaseRangeState): boolean { + return ( + this.input1 === other.input1 && + this.input2 === other.input2 && + this.input2First === other.input2First && + this.conflicting === other.conflicting + ); } } diff --git a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts index ea1a9794545..ebef4581ab6 100644 --- a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts +++ b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts @@ -8,7 +8,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { transaction } from 'vs/base/common/observable'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { Range } from 'vs/editor/common/core/range'; -import { ITextModel } from 'vs/editor/common/model'; +import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model'; import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker'; import { createModelServices, createTextModel } from 'vs/editor/test/common/testTextModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -29,9 +29,10 @@ suite('merge editor model', () => { }, model => { assert.deepStrictEqual(model.getProjections(), { - base: '⟦⟧₀line1\nline2', - input1: '⟦0\n⟧₀line1\nline2', - input2: '⟦0\n⟧₀line1\nline2', + base: ['⟦⟧₀line1', 'line2'], + input1: ['⟦0', '⟧₀line1', 'line2'], + input2: ['⟦0', '⟧₀line1', 'line2'], + result: ['⟦⟧{conflicting}₀'], }); model.toggleConflict(0, 1); @@ -59,7 +60,12 @@ suite('merge editor model', () => { "result": "" }, model => { - assert.deepStrictEqual(model.getProjections(), ({ base: "⟦⟧₀", input1: "⟦input1⟧₀", input2: "⟦input2⟧₀" })); + assert.deepStrictEqual(model.getProjections(), { + base: ['⟦⟧₀'], + input1: ['⟦input1⟧₀'], + input2: ['⟦input2⟧₀'], + result: ['⟦⟧{}₀'], + }); model.toggleConflict(0, 1); assert.deepStrictEqual( @@ -87,9 +93,10 @@ suite('merge editor model', () => { }, model => { assert.deepStrictEqual(model.getProjections(), { - base: '⟦hello⟧₀', - input1: '⟦hallo⟧₀', - input2: '⟦helloworld⟧₀', + base: ['⟦hello⟧₀'], + input1: ['⟦hallo⟧₀'], + input2: ['⟦helloworld⟧₀'], + result: ['⟦⟧{conflicting}₀'], }); model.toggleConflict(0, 1); @@ -115,11 +122,34 @@ suite('merge editor model', () => { }, model => { assert.deepStrictEqual(model.getProjections(), { - base: 'Zürich\nBern\n⟦Basel\n⟧₀Chur\n⟦⟧₁Genf\nThun⟦⟧₂', - input1: - 'Zürich\nBern\n⟦⟧₀Chur\n⟦Davos\n⟧₁Genf\nThun\n⟦function f(b:boolean) {}⟧₂', - input2: - 'Zürich\nBern\n⟦Basel (FCB)\n⟧₀Chur\n⟦⟧₁Genf\nThun\n⟦function f(a:number) {}⟧₂', + base: ['Zürich', 'Bern', '⟦Basel', '⟧₀Chur', '⟦⟧₁Genf', 'Thun⟦⟧₂'], + input1: [ + 'Zürich', + 'Bern', + '⟦⟧₀Chur', + '⟦Davos', + '⟧₁Genf', + 'Thun', + '⟦function f(b:boolean) {}⟧₂', + ], + input2: [ + 'Zürich', + 'Bern', + '⟦Basel (FCB)', + '⟧₀Chur', + '⟦⟧₁Genf', + 'Thun', + '⟦function f(a:number) {}⟧₂', + ], + result: [ + 'Zürich', + 'Bern', + '⟦Basel', + '⟧{}₀Chur', + '⟦Davos', + '⟧{1✓}₁Genf', + 'Thun⟦⟧{}₂', + ], }); model.toggleConflict(2, 1); @@ -135,6 +165,61 @@ suite('merge editor model', () => { } ); }); + + test('conflicts are reset', async () => { + await testMergeModel( + { + "languageId": "typescript", + "base": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { EditorOption } from 'vs/editor/common/config/editorOptions';\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n", + "input1": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { observableSignalFromEvent } from 'vs/base/common/observable';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n", + "input2": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n", + "result": "import { h } from 'vs/base/browser/dom';\r\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\r\nimport { observableSignalFromEvent } from 'vs/base/common/observable';\r\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\r\n<<<<<<< Updated upstream\r\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\r\n=======\r\nimport { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';\r\n>>>>>>> Stashed changes\r\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\r\n" + }, + model => { + assert.deepStrictEqual(model.getProjections(), { + base: [ + "import { h } from 'vs/base/browser/dom';", + "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';", + "⟦⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';", + "⟦import { EditorOption } from 'vs/editor/common/config/editorOptions';", + "import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';", + "⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';", + '', + ], + input1: [ + "import { h } from 'vs/base/browser/dom';", + "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';", + "⟦import { observableSignalFromEvent } from 'vs/base/common/observable';", + "⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';", + "⟦import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';", + "⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';", + '', + ], + input2: [ + "import { h } from 'vs/base/browser/dom';", + "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';", + "⟦⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';", + "⟦import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';", + "⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';", + '', + ], + result: [ + "import { h } from 'vs/base/browser/dom';", + "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';", + "⟦import { observableSignalFromEvent } from 'vs/base/common/observable';", + "⟧{1✓}₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';", + '⟦<<<<<<< Updated upstream', + "import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';", + '=======', + "import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';", + '>>>>>>> Stashed changes', + "⟧{conflicting}₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';", + '', + ], + }); + } + ); + }); }); async function testMergeModel( @@ -197,7 +282,9 @@ class MergeModelInterface extends Disposable { ), }; }, - } + }, { + resetUnknownOnInitialization: false + } )); } @@ -241,14 +328,25 @@ class MergeModelInterface extends Disposable { })) ); + const resultTextModel = createTextModel(this.mergeModel.result.getValue()); + applyRanges( + resultTextModel, + baseRanges.map((r, idx) => ({ + range: this.mergeModel.getRangeInResult(r.baseRange).toRange(), + label: `{${this.mergeModel.getState(r).get()}}${toSmallNumbersDec(idx)}`, + })) + ); + const result = { - base: baseTextModel.getValue(), - input1: input1TextModel.getValue(), - input2: input2TextModel.getValue(), + base: baseTextModel.getValue(EndOfLinePreference.LF).split('\n'), + input1: input1TextModel.getValue(EndOfLinePreference.LF).split('\n'), + input2: input2TextModel.getValue(EndOfLinePreference.LF).split('\n'), + result: resultTextModel.getValue(EndOfLinePreference.LF).split('\n'), }; baseTextModel.dispose(); input1TextModel.dispose(); input2TextModel.dispose(); + resultTextModel.dispose(); return result; } -- cgit v1.2.3 From 1c5723822a33d7c40913f98d8a2f639b40e7f8ad Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 5 Jul 2022 14:51:34 +0200 Subject: Comments editor should respect autoClosingBrackets (#154154) Fixes #150003 --- src/vs/workbench/contrib/comments/browser/commentNode.ts | 2 +- src/vs/workbench/contrib/comments/browser/commentReply.ts | 4 +++- src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index d1d118f5c15..450c76ae8ad 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -382,7 +382,7 @@ export class CommentNode extends Disposable { private createCommentEditor(editContainer: HTMLElement): void { const container = dom.append(editContainer, dom.$('.edit-textarea')); - this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, container, SimpleCommentEditor.getEditorOptions(), this.parentThread); + this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, container, SimpleCommentEditor.getEditorOptions(this.configurationService), this.parentThread); const resource = URI.parse(`comment:commentinput-${this.comment.uniqueIdInThread}-${Date.now()}.md`); this._commentEditorModel = this.modelService.createModel('', this.languageService.createByFilepathOrFirstLine(resource), resource, false); diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index 0905db307c3..8fe2a07420e 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -17,6 +17,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import * as nls from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry'; @@ -58,11 +59,12 @@ export class CommentReply extends Disposable { @ILanguageService private languageService: ILanguageService, @IModelService private modelService: IModelService, @IThemeService private themeService: IThemeService, + @IConfigurationService configurationService: IConfigurationService ) { super(); this.form = dom.append(container, dom.$('.comment-form')); - this.commentEditor = this._register(this._scopedInstatiationService.createInstance(SimpleCommentEditor, this.form, SimpleCommentEditor.getEditorOptions(), this._parentThread)); + this.commentEditor = this._register(this._scopedInstatiationService.createInstance(SimpleCommentEditor, this.form, SimpleCommentEditor.getEditorOptions(configurationService), this._parentThread)); this.commentEditorIsEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService); this.commentEditorIsEmpty.set(!this._pendingComment); diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index 4a69955fd4a..c4ec1bbf5e3 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -24,6 +24,7 @@ import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/comme import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export const ctxCommentEditorFocused = new RawContextKey('commentEditorFocused', false); @@ -79,7 +80,7 @@ export class SimpleCommentEditor extends CodeEditorWidget { return EditorExtensionsRegistry.getEditorActions(); } - public static getEditorOptions(): IEditorOptions { + public static getEditorOptions(configurationService: IConfigurationService): IEditorOptions { return { wordWrap: 'on', glyphMargin: false, @@ -103,6 +104,7 @@ export class SimpleCommentEditor extends CodeEditorWidget { minimap: { enabled: false }, + autoClosingBrackets: configurationService.getValue('editor.autoClosingBrackets'), quickSuggestions: false }; } -- cgit v1.2.3 From 25cf08709abc75b73db7732ff03800189ca9cff9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 5 Jul 2022 06:38:02 -0700 Subject: Add ellipsis to recent command/dir commands Fixes #153905 --- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index ed78d4a79e8..0c70784c0e3 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -307,7 +307,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.RunRecentCommand, - title: { value: localize('workbench.action.terminal.runRecentCommand', "Run Recent Command"), original: 'Run Recent Command' }, + title: { value: localize('workbench.action.terminal.runRecentCommand', "Run Recent Command..."), original: 'Run Recent Command...' }, f1: true, category, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated) @@ -331,7 +331,7 @@ export function registerTerminalActions() { constructor() { super({ id: TerminalCommandId.GoToRecentDirectory, - title: { value: localize('workbench.action.terminal.goToRecentDirectory', "Go to Recent Directory"), original: 'Go to Recent Directory' }, + title: { value: localize('workbench.action.terminal.goToRecentDirectory', "Go to Recent Directory..."), original: 'Go to Recent Directory...' }, f1: true, category, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated) -- cgit v1.2.3 From 406aa3e7e6a63f68e7ecd68d6ea628b8b0e740d9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 5 Jul 2022 07:40:36 -0700 Subject: Enable auto shell integration by default Fixes #154161 --- src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index efe3aa79bd3..83a67239048 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -536,7 +536,7 @@ const terminalConfiguration: IConfigurationNode = { restricted: true, markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Enable features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives VS Code insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, pwsh, zsh\n - Windows: pwsh\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup."), type: 'boolean', - default: false + default: true }, [TerminalSettingId.ShellIntegrationDecorationsEnabled]: { restricted: true, -- cgit v1.2.3 From 3fc3965c15b5e22014636a5be08f2cbdb7e05bec Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 5 Jul 2022 07:54:19 -0700 Subject: Support detecting eslint compact link format in term Fixes #154165 --- .../contrib/terminal/browser/links/terminalLocalLinkDetector.ts | 2 +- .../terminal/test/browser/links/terminalLocalLinkDetector.test.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts index 48e5c0659bc..19cab952add 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts @@ -52,7 +52,7 @@ export const lineAndColumnClause = [ '((\\S*)[\'"], line ((\\d+)( column (\\d+))?))', // "(file path)", line 45 [see #40468] '((\\S*)[\'"],((\\d+)(:(\\d+))?))', // "(file path)",45 [see #78205] '((\\S*) on line ((\\d+)(, column (\\d+))?))', // (file path) on line 8, column 13 - '((\\S*):line ((\\d+)(, column (\\d+))?))', // (file path):line 8, column 13 + '((\\S*):\\s?line ((\\d+)(, col(umn)? (\\d+))?))', // (file path):line 8, column 13, (file path): line 8, col 13 '(([^\\s\\(\\)]*)(\\s?[\\(\\[](\\d+)(,\\s?(\\d+))?)[\\)\\]])', // (file path)(45), (file path) (45), (file path)(45,18), (file path) (45,18), (file path)(45, 18), (file path) (45, 18), also with [] '(([^:\\s\\(\\)<>\'\"\\[\\]]*)(:(\\d+))?(:(\\d+))?)' // (file path):336, (file path):336:9 ].join('|').replace(/ /g, `[${'\u00A0'} ]`); diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts index 8ac3607d6e8..9f72ece8c18 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts @@ -58,6 +58,8 @@ const supportedLinkFormats: LinkFormatInfo[] = [ { urlFormat: '{0} on line {1}, column {2}', line: '5', column: '3' }, { urlFormat: '{0}:line {1}', line: '5' }, { urlFormat: '{0}:line {1}, column {2}', line: '5', column: '3' }, + { urlFormat: '{0}: line {1}', line: '5' }, + { urlFormat: '{0}: line {1}, col {2}', line: '5', column: '3' }, { urlFormat: '{0}({1})', line: '5' }, { urlFormat: '{0} ({1})', line: '5' }, { urlFormat: '{0}({1},{2})', line: '5', column: '3' }, -- cgit v1.2.3 From 62a2b0509aa29d06f7220d8f3527e67d2b974f2b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 5 Jul 2022 20:42:05 +0200 Subject: Clean up in profiles land (#154188) Clean up: - Move preserving data from existing profile to respective components --- src/vs/workbench/contrib/snippets/browser/snippetsService.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 366ed64536b..abc007e863d 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -358,7 +358,12 @@ class SnippetsService implements ISnippetsService { await this._initFolderSnippets(SnippetSource.User, userSnippetsFolder, disposables); }; this._disposables.add(disposables); - this._disposables.add(this._userDataProfileService.onDidChangeCurrentProfile(() => this._pendingWork.push(updateUserSnippets()))); + this._disposables.add(this._userDataProfileService.onDidChangeCurrentProfile(e => e.join((async () => { + if (e.preserveData) { + await this._fileService.copy(e.previous.snippetsHome, e.profile.snippetsHome); + } + this._pendingWork.push(updateUserSnippets()); + })()))); await updateUserSnippets(); } -- cgit v1.2.3 From 71c221c532996c9976405f62bb888283c0cf6545 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 5 Jul 2022 21:30:01 +0200 Subject: joh/theoretical quokka (#154157) * add `SnippetController#apply(ISnippetEdit[])` This replaces the initial ugly trick with a more sound implementation of arbitrary snippet edits. A snippet edit can cover disconnected regions, each will be applied as separate text edit but everything will become a single `OneSnippet` instance * add integration test for SnippetString-text edit inside workspace edit --- .../contrib/bulkEdit/browser/bulkTextEdits.ts | 23 ++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts index f69e60201f2..b07897a513a 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts @@ -19,8 +19,9 @@ import { ResourceMap } from 'vs/base/common/map'; import { IModelService } from 'vs/editor/common/services/model'; import { ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { performSnippetEdits } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; +import { ISnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetSession'; type ValidationResult = { canApply: true } | { canApply: false; reason: URI }; @@ -141,14 +142,24 @@ class EditorEditTask extends ModelEditTask { super.apply(); return; } - if (this._edits.length > 0) { - const insertAsSnippet = this._edits.every(edit => edit.insertAsSnippet); - if (insertAsSnippet) { - // todo@jrieken what ABOUT EOL? - performSnippetEdits(this._editor, this._edits.map(edit => ({ range: Range.lift(edit.range!), snippet: edit.text! }))); + if (this._edits.length > 0) { + const snippetCtrl = SnippetController2.get(this._editor); + if (snippetCtrl && this._edits.some(edit => edit.insertAsSnippet)) { + // some edit is a snippet edit -> use snippet controller and ISnippetEdits + const snippetEdits: ISnippetEdit[] = []; + for (const edit of this._edits) { + if (edit.range && edit.text !== null) { + snippetEdits.push({ + range: Range.lift(edit.range), + template: edit.insertAsSnippet ? edit.text : SnippetParser.escape(edit.text) + }); + } + } + snippetCtrl.apply(snippetEdits); } else { + // normal edit this._edits = this._edits .map(this._transformSnippetStringToInsertText, this) // mixed edits (snippet and normal) -> no snippet mode .sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); -- cgit v1.2.3 From 3e59037fa15db36fd2dca02b1546f8e8ac2e6d80 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 5 Jul 2022 12:54:14 -0700 Subject: Debt - Add dedicated Edit Sessions output channel (#154190) * Create a separate log channel for edit sessions * Move edit sessions services into contrib since they are not accessed from outside the contrib * Remove redundant log message prefix * Update test --- .../workbench/contrib/logs/common/logConstants.ts | 1 + .../contrib/logs/common/logs.contribution.ts | 1 + .../browser/sessionSync.contribution.ts | 41 +-- .../browser/sessionSyncWorkbenchService.ts | 358 +++++++++++++++++++++ .../sessionSync/common/editSessionsLogService.ts | 50 +++ .../contrib/sessionSync/common/sessionSync.ts | 67 ++++ .../sessionSync/test/browser/sessionSync.test.ts | 6 +- 7 files changed, 502 insertions(+), 22 deletions(-) create mode 100644 src/vs/workbench/contrib/sessionSync/browser/sessionSyncWorkbenchService.ts create mode 100644 src/vs/workbench/contrib/sessionSync/common/editSessionsLogService.ts create mode 100644 src/vs/workbench/contrib/sessionSync/common/sessionSync.ts (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/logs/common/logConstants.ts b/src/vs/workbench/contrib/logs/common/logConstants.ts index 9ba3e7aa0ee..4342dd4a740 100644 --- a/src/vs/workbench/contrib/logs/common/logConstants.ts +++ b/src/vs/workbench/contrib/logs/common/logConstants.ts @@ -9,5 +9,6 @@ export const rendererLogChannelId = 'rendererLog'; export const extHostLogChannelId = 'extHostLog'; export const telemetryLogChannelId = 'telemetryLog'; export const userDataSyncLogChannelId = 'userDataSyncLog'; +export const editSessionsLogChannelId = 'editSessionsSyncLog'; export const showWindowLogActionId = 'workbench.action.showWindowLog'; diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 5e533149305..f45fe793b09 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -38,6 +38,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { private registerCommonContributions(): void { this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Settings Sync"), this.environmentService.userDataSyncLogResource); + this.registerLogChannel(Constants.editSessionsLogChannelId, nls.localize('editSessionsLog', "Edit Sessions"), this.environmentService.editSessionsLogResource); this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile); const registerTelemetryChannel = () => { diff --git a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts index 08381fc3d9c..642ab0ce273 100644 --- a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts +++ b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts @@ -10,7 +10,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; -import { ISessionSyncWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EditSessionSchemaVersion } from 'vs/workbench/services/sessionSync/common/sessionSync'; +import { ISessionSyncWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EditSessionSchemaVersion, IEditSessionsLogService } from 'vs/workbench/contrib/sessionSync/common/sessionSync'; import { ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -19,13 +19,12 @@ import { joinPath, relativePath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { SessionSyncWorkbenchService } from 'vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService'; +import { SessionSyncWorkbenchService } from 'vs/workbench/contrib/sessionSync/browser/sessionSyncWorkbenchService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { UserDataSyncErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -39,7 +38,9 @@ import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtua import { Schemas } from 'vs/base/common/network'; import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { EditSessionsLogService } from 'vs/workbench/contrib/sessionSync/common/editSessionsLogService'; +registerSingleton(IEditSessionsLogService, EditSessionsLogService); registerSingleton(ISessionSyncWorkbenchService, SessionSyncWorkbenchService); const resumeLatestCommand = { @@ -61,6 +62,7 @@ const openLocalFolderCommand = { title: { value: localize('continue edit session in local folder', "Open In Local Folder"), original: 'Open In Local Folder' }, }; const queryParamName = 'editSessionId'; +const experimentalSettingName = 'workbench.experimental.editSessions.enabled'; export class SessionSyncContribution extends Disposable implements IWorkbenchContribution { @@ -76,7 +78,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon @ISCMService private readonly scmService: ISCMService, @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, - @ILogService private readonly logService: ILogService, + @IEditSessionsLogService private readonly logService: IEditSessionsLogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IProductService private readonly productService: IProductService, @IConfigurationService private configurationService: IConfigurationService, @@ -93,7 +95,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon } this.configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration('workbench.experimental.editSessions.enabled')) { + if (e.affectsConfiguration(experimentalSettingName)) { this.registerActions(); } }); @@ -129,7 +131,8 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon } private registerActions() { - if (this.registered || this.configurationService.getValue('workbench.experimental.editSessions.enabled') !== true) { + if (this.registered || this.configurationService.getValue(experimentalSettingName) !== true) { + this.logService.info(`Skipping registering edit sessions actions as edit sessions are currently disabled. Set ${experimentalSettingName} to enable edit sessions.`); return; } @@ -167,11 +170,11 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon query: uri.query.length > 0 ? (uri + `&${queryParamName}=${encodedRef}`) : `${queryParamName}=${encodedRef}` }); } else { - that.logService.warn(`Edit Sessions: Failed to store edit session when invoking ${continueEditSessionCommand.id}.`); + that.logService.warn(`Failed to store edit session when invoking ${continueEditSessionCommand.id}.`); } // Open the URI - that.logService.info(`Edit Sessions: opening ${uri.toString()}`); + that.logService.info(`Opening ${uri.toString()}`); await that.openerService.open(uri, { openExternal: true }); } })); @@ -221,7 +224,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon async applyEditSession(ref?: string): Promise { if (ref !== undefined) { - this.logService.info(`Edit Sessions: Applying edit session with ref ${ref}.`); + this.logService.info(`Applying edit session with ref ${ref}.`); } const data = await this.sessionSyncWorkbenchService.read(ref); @@ -231,7 +234,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon } else { this.notificationService.warn(localize('no edit session content for ref', 'Could not apply edit session contents for ID {0}.', ref)); } - this.logService.info(`Edit Sessions: Aborting applying edit session as no edit session content is available to be applied from ref ${ref}.`); + this.logService.info(`Aborting applying edit session as no edit session content is available to be applied from ref ${ref}.`); return; } const editSession = data.editSession; @@ -249,7 +252,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon for (const folder of editSession.folders) { const folderRoot = this.contextService.getWorkspace().folders.find((f) => f.name === folder.name); if (!folderRoot) { - this.logService.info(`Edit Sessions: Skipping applying ${folder.workingChanges.length} changes from edit session with ref ${ref} as no corresponding workspace folder named ${folder.name} is currently open.`); + this.logService.info(`Skipping applying ${folder.workingChanges.length} changes from edit session with ref ${ref} as no corresponding workspace folder named ${folder.name} is currently open.`); continue; } @@ -289,11 +292,11 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon } } - this.logService.info(`Edit Sessions: Deleting edit session with ref ${ref} after successfully applying it to current workspace...`); + this.logService.info(`Deleting edit session with ref ${ref} after successfully applying it to current workspace...`); await this.sessionSyncWorkbenchService.delete(ref); - this.logService.info(`Edit Sessions: Deleted edit session with ref ${ref}.`); + this.logService.info(`Deleted edit session with ref ${ref}.`); } catch (ex) { - this.logService.error('Edit Sessions: Failed to apply edit session, reason: ', (ex as Error).toString()); + this.logService.error('Failed to apply edit session, reason: ', (ex as Error).toString()); this.notificationService.error(localize('apply failed', "Failed to apply your edit session.")); } } @@ -312,7 +315,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon for (const uri of trackedUris) { const workspaceFolder = this.contextService.getWorkspaceFolder(uri); if (!workspaceFolder) { - this.logService.info(`Edit Sessions: Skipping working change ${uri.toString()} as no associated workspace folder was found.`); + this.logService.info(`Skipping working change ${uri.toString()} as no associated workspace folder was found.`); continue; } @@ -341,7 +344,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon } if (!hasEdits) { - this.logService.info('Edit Sessions: Skipping storing edit session as there are no edits to store.'); + this.logService.info('Skipping storing edit session as there are no edits to store.'); if (fromStoreCommand) { this.notificationService.info(localize('no edits to store', 'Skipped storing edit session as there are no edits to store.')); } @@ -351,12 +354,12 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon const data: EditSession = { folders, version: 1 }; try { - this.logService.info(`Edit Sessions: Storing edit session...`); + this.logService.info(`Storing edit session...`); const ref = await this.sessionSyncWorkbenchService.write(data); - this.logService.info(`Edit Sessions: Stored edit session with ref ${ref}.`); + this.logService.info(`Stored edit session with ref ${ref}.`); return ref; } catch (ex) { - this.logService.error(`Edit Sessions: Failed to store edit session, reason: `, (ex as Error).toString()); + this.logService.error(`Failed to store edit session, reason: `, (ex as Error).toString()); type UploadFailedEvent = { reason: string }; type UploadFailedClassification = { diff --git a/src/vs/workbench/contrib/sessionSync/browser/sessionSyncWorkbenchService.ts b/src/vs/workbench/contrib/sessionSync/browser/sessionSyncWorkbenchService.ts new file mode 100644 index 00000000000..1df0ecc3d28 --- /dev/null +++ b/src/vs/workbench/contrib/sessionSync/browser/sessionSyncWorkbenchService.ts @@ -0,0 +1,358 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, ISessionSyncWorkbenchService, EDIT_SESSIONS_SIGNED_IN_KEY, IEditSessionsLogService } from 'vs/workbench/contrib/sessionSync/common/sessionSync'; + +type ExistingSession = IQuickPickItem & { session: AuthenticationSession & { providerId: string } }; +type AuthenticationProviderOption = IQuickPickItem & { provider: IAuthenticationProvider }; + +export class SessionSyncWorkbenchService extends Disposable implements ISessionSyncWorkbenchService { + + _serviceBrand = undefined; + + private serverConfiguration = this.productService['sessionSync.store']; + private storeClient: UserDataSyncStoreClient | undefined; + + #authenticationInfo: { sessionId: string; token: string; providerId: string } | undefined; + private static CACHED_SESSION_STORAGE_KEY = 'editSessionSyncAccountPreference'; + + private initialized = false; + private readonly signedInContext: IContextKey; + + constructor( + @IFileService private readonly fileService: IFileService, + @IStorageService private readonly storageService: IStorageService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @IExtensionService private readonly extensionService: IExtensionService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEditSessionsLogService private readonly logService: IEditSessionsLogService, + @IProductService private readonly productService: IProductService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IRequestService private readonly requestService: IRequestService, + ) { + super(); + + // If the user signs out of the current session, reset our cached auth state in memory and on disk + this._register(this.authenticationService.onDidChangeSessions((e) => this.onDidChangeSessions(e.event))); + + // If another window changes the preferred session storage, reset our cached auth state in memory + this._register(this.storageService.onDidChangeValue(e => this.onDidChangeStorage(e))); + + this.registerResetAuthenticationAction(); + + this.signedInContext = EDIT_SESSIONS_SIGNED_IN.bindTo(this.contextKeyService); + this.signedInContext.set(this.existingSessionId !== undefined); + } + + /** + * + * @param editSession An object representing edit session state to be restored. + * @returns The ref of the stored edit session state. + */ + async write(editSession: EditSession): Promise { + await this.initialize(); + if (!this.initialized) { + throw new Error('Please sign in to store your edit session.'); + } + + return this.storeClient!.write('editSessions', JSON.stringify(editSession), null); + } + + /** + * @param ref: A specific content ref to retrieve content for, if it exists. + * If undefined, this method will return the latest saved edit session, if any. + * + * @returns An object representing the requested or latest edit session state, if any. + */ + async read(ref: string | undefined): Promise<{ ref: string; editSession: EditSession } | undefined> { + await this.initialize(); + if (!this.initialized) { + throw new Error('Please sign in to apply your latest edit session.'); + } + + let content: string | undefined | null; + try { + if (ref !== undefined) { + content = await this.storeClient?.resolveContent('editSessions', ref); + } else { + const result = await this.storeClient?.read('editSessions', null); + content = result?.content; + ref = result?.ref; + } + } catch (ex) { + this.logService.error(ex); + } + + // TODO@joyceerhl Validate session data, check schema version + return (content !== undefined && content !== null && ref !== undefined) ? { ref: ref, editSession: JSON.parse(content) } : undefined; + } + + async delete(ref: string) { + await this.initialize(); + if (!this.initialized) { + throw new Error(`Unable to delete edit session with ref ${ref}.`); + } + + try { + await this.storeClient?.delete('editSessions', ref); + } catch (ex) { + this.logService.error(ex); + } + } + + private async initialize() { + if (this.initialized) { + return; + } + this.initialized = await this.doInitialize(); + this.signedInContext.set(this.initialized); + } + + /** + * + * Ensures that the store client is initialized, + * meaning that authentication is configured and it + * can be used to communicate with the remote storage service + */ + private async doInitialize(): Promise { + // Wait for authentication extensions to be registered + await this.extensionService.whenInstalledExtensionsRegistered(); + + if (!this.serverConfiguration?.url) { + throw new Error('Unable to initialize sessions sync as session sync preference is not configured in product.json.'); + } + + if (!this.storeClient) { + this.storeClient = new UserDataSyncStoreClient(URI.parse(this.serverConfiguration.url), this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService); + this._register(this.storeClient.onTokenFailed(() => { + this.logService.info('Clearing edit sessions authentication preference because of successive token failures.'); + this.clearAuthenticationPreference(); + })); + } + + // If we already have an existing auth session in memory, use that + if (this.#authenticationInfo !== undefined) { + return true; + } + + // If the user signed in previously and the session is still available, reuse that without prompting the user again + const existingSessionId = this.existingSessionId; + if (existingSessionId) { + this.logService.trace(`Searching for existing authentication session with ID ${existingSessionId}`); + const existing = await this.getExistingSession(); + if (existing !== undefined) { + this.logService.trace(`Found existing authentication session with ID ${existingSessionId}`); + this.#authenticationInfo = { sessionId: existing.session.id, token: existing.session.accessToken, providerId: existing.session.providerId }; + this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId); + return true; + } + } + + // Ask the user to pick a preferred account + const session = await this.getAccountPreference(); + if (session !== undefined) { + this.#authenticationInfo = { sessionId: session.id, token: session.accessToken, providerId: session.providerId }; + this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId); + this.existingSessionId = session.id; + this.logService.trace(`Saving authentication session preference for ID ${session.id}.`); + return true; + } + + return false; + } + + /** + * + * Prompts the user to pick an authentication option for storing and getting edit sessions. + */ + private async getAccountPreference(): Promise { + const quickpick = this.quickInputService.createQuickPick(); + quickpick.title = localize('account preference', 'Sign In to Use Edit Sessions'); + quickpick.ok = false; + quickpick.placeholder = localize('choose account placeholder', "Select an account to sign in"); + quickpick.ignoreFocusOut = true; + quickpick.items = await this.createQuickpickItems(); + + return new Promise((resolve, reject) => { + quickpick.onDidHide((e) => { + resolve(undefined); + quickpick.dispose(); + }); + + quickpick.onDidAccept(async (e) => { + const selection = quickpick.selectedItems[0]; + const session = 'provider' in selection ? { ...await this.authenticationService.createSession(selection.provider.id, selection.provider.scopes), providerId: selection.provider.id } : selection.session; + resolve(session); + quickpick.hide(); + }); + + quickpick.show(); + }); + } + + private async createQuickpickItems(): Promise<(ExistingSession | AuthenticationProviderOption | IQuickPickSeparator)[]> { + const options: (ExistingSession | AuthenticationProviderOption | IQuickPickSeparator)[] = []; + + options.push({ type: 'separator', label: localize('signed in', "Signed In") }); + + const sessions = await this.getAllSessions(); + options.push(...sessions); + + options.push({ type: 'separator', label: localize('others', "Others") }); + + for (const authenticationProvider of (await this.getAuthenticationProviders())) { + const signedInForProvider = sessions.some(account => account.session.providerId === authenticationProvider.id); + if (!signedInForProvider || this.authenticationService.supportsMultipleAccounts(authenticationProvider.id)) { + const providerName = this.authenticationService.getLabel(authenticationProvider.id); + options.push({ label: localize('sign in using account', "Sign in with {0}", providerName), provider: authenticationProvider }); + } + } + + return options; + } + + /** + * + * Returns all authentication sessions available from {@link getAuthenticationProviders}. + */ + private async getAllSessions() { + const authenticationProviders = await this.getAuthenticationProviders(); + const accounts = new Map(); + let currentSession: ExistingSession | undefined; + + for (const provider of authenticationProviders) { + const sessions = await this.authenticationService.getSessions(provider.id, provider.scopes); + + for (const session of sessions) { + const item = { + label: session.account.label, + description: this.authenticationService.getLabel(provider.id), + session: { ...session, providerId: provider.id } + }; + accounts.set(item.session.account.id, item); + if (this.existingSessionId === session.id) { + currentSession = item; + } + } + } + + if (currentSession !== undefined) { + accounts.set(currentSession.session.account.id, currentSession); + } + + return [...accounts.values()]; + } + + /** + * + * Returns all authentication providers which can be used to authenticate + * to the remote storage service, based on product.json configuration + * and registered authentication providers. + */ + private async getAuthenticationProviders() { + if (!this.serverConfiguration) { + throw new Error('Unable to get configured authentication providers as session sync preference is not configured in product.json.'); + } + + // Get the list of authentication providers configured in product.json + const authenticationProviders = this.serverConfiguration.authenticationProviders; + const configuredAuthenticationProviders = Object.keys(authenticationProviders).reduce((result, id) => { + result.push({ id, scopes: authenticationProviders[id].scopes }); + return result; + }, []); + + // Filter out anything that isn't currently available through the authenticationService + const availableAuthenticationProviders = this.authenticationService.declaredProviders; + + return configuredAuthenticationProviders.filter(({ id }) => availableAuthenticationProviders.some(provider => provider.id === id)); + } + + private get existingSessionId() { + return this.storageService.get(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION); + } + + private set existingSessionId(sessionId: string | undefined) { + if (sessionId === undefined) { + this.storageService.remove(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION); + } else { + this.storageService.store(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, sessionId, StorageScope.APPLICATION, StorageTarget.MACHINE); + } + } + + private async getExistingSession() { + const accounts = await this.getAllSessions(); + return accounts.find((account) => account.session.id === this.existingSessionId); + } + + private async onDidChangeStorage(e: IStorageValueChangeEvent): Promise { + if (e.key === SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY + && e.scope === StorageScope.APPLICATION + ) { + const newSessionId = this.existingSessionId; + const previousSessionId = this.#authenticationInfo?.sessionId; + + if (previousSessionId !== newSessionId) { + this.logService.trace(`Resetting authentication state because authentication session ID preference changed from ${previousSessionId} to ${newSessionId}.`); + this.#authenticationInfo = undefined; + this.initialized = false; + } + } + } + + private clearAuthenticationPreference(): void { + this.#authenticationInfo = undefined; + this.initialized = false; + this.existingSessionId = undefined; + this.signedInContext.set(false); + } + + private onDidChangeSessions(e: AuthenticationSessionsChangeEvent): void { + if (this.#authenticationInfo?.sessionId && e.removed.find(session => session.id === this.#authenticationInfo?.sessionId)) { + this.clearAuthenticationPreference(); + } + } + + private registerResetAuthenticationAction() { + const that = this; + this._register(registerAction2(class ResetEditSessionAuthenticationAction extends Action2 { + constructor() { + super({ + id: 'workbench.sessionSync.actions.resetAuth', + title: localize('reset auth', 'Sign Out'), + category: EDIT_SESSION_SYNC_CATEGORY, + precondition: ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, true), + menu: [{ + id: MenuId.CommandPalette, + }, + { + id: MenuId.AccountsContext, + group: '2_editSessions', + when: ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, true), + }] + }); + } + + run() { + that.clearAuthenticationPreference(); + } + })); + } +} diff --git a/src/vs/workbench/contrib/sessionSync/common/editSessionsLogService.ts b/src/vs/workbench/contrib/sessionSync/common/editSessionsLogService.ts new file mode 100644 index 00000000000..2b3b6bca671 --- /dev/null +++ b/src/vs/workbench/contrib/sessionSync/common/editSessionsLogService.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { AbstractLogger, ILogger, ILoggerService } from 'vs/platform/log/common/log'; +import { IEditSessionsLogService } from 'vs/workbench/contrib/sessionSync/common/sessionSync'; + +export class EditSessionsLogService extends AbstractLogger implements IEditSessionsLogService { + + declare readonly _serviceBrand: undefined; + private readonly logger: ILogger; + + constructor( + @ILoggerService loggerService: ILoggerService, + @IEnvironmentService environmentService: IEnvironmentService + ) { + super(); + this.logger = this._register(loggerService.createLogger(environmentService.editSessionsLogResource, { name: 'editsessions' })); + } + + trace(message: string, ...args: any[]): void { + this.logger.trace(message, ...args); + } + + debug(message: string, ...args: any[]): void { + this.logger.debug(message, ...args); + } + + info(message: string, ...args: any[]): void { + this.logger.info(message, ...args); + } + + warn(message: string, ...args: any[]): void { + this.logger.warn(message, ...args); + } + + error(message: string | Error, ...args: any[]): void { + this.logger.error(message, ...args); + } + + critical(message: string | Error, ...args: any[]): void { + this.logger.critical(message, ...args); + } + + flush(): void { + this.logger.flush(); + } +} diff --git a/src/vs/workbench/contrib/sessionSync/common/sessionSync.ts b/src/vs/workbench/contrib/sessionSync/common/sessionSync.ts new file mode 100644 index 00000000000..538a54a6444 --- /dev/null +++ b/src/vs/workbench/contrib/sessionSync/common/sessionSync.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { ILocalizedString } from 'vs/platform/action/common/action'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; + +export const EDIT_SESSION_SYNC_CATEGORY: ILocalizedString = { + original: 'Edit Sessions', + value: localize('session sync', 'Edit Sessions') +}; + +export const ISessionSyncWorkbenchService = createDecorator('ISessionSyncWorkbenchService'); +export interface ISessionSyncWorkbenchService { + _serviceBrand: undefined; + + read(ref: string | undefined): Promise<{ ref: string; editSession: EditSession } | undefined>; + write(editSession: EditSession): Promise; + delete(ref: string): Promise; +} + +export const IEditSessionsLogService = createDecorator('IEditSessionsLogService'); +export interface IEditSessionsLogService extends ILogService { } + +export enum ChangeType { + Addition = 1, + Deletion = 2, +} + +export enum FileType { + File = 1, +} + +interface Addition { + relativeFilePath: string; + fileType: FileType.File; + contents: string; + type: ChangeType.Addition; +} + +interface Deletion { + relativeFilePath: string; + fileType: FileType.File; + contents: undefined; + type: ChangeType.Deletion; +} + +export type Change = Addition | Deletion; + +export interface Folder { + name: string; + workingChanges: Change[]; +} + +export const EditSessionSchemaVersion = 1; + +export interface EditSession { + version: number; + folders: Folder[]; +} + +export const EDIT_SESSIONS_SIGNED_IN_KEY = 'editSessionsSignedIn'; +export const EDIT_SESSIONS_SIGNED_IN = new RawContextKey(EDIT_SESSIONS_SIGNED_IN_KEY, false); diff --git a/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts b/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts index 5c29394116b..140c068d5d0 100644 --- a/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts +++ b/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts @@ -9,7 +9,7 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas } from 'vs/base/common/network'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { NullLogService, ILogService } from 'vs/platform/log/common/log'; +import { NullLogService } from 'vs/platform/log/common/log'; import { SessionSyncContribution } from 'vs/workbench/contrib/sessionSync/browser/sessionSync.contribution'; import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; import { IProgressService } from 'vs/platform/progress/common/progress'; @@ -21,7 +21,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { mock } from 'vs/base/test/common/mock'; import * as sinon from 'sinon'; import * as assert from 'assert'; -import { ChangeType, FileType, ISessionSyncWorkbenchService } from 'vs/workbench/services/sessionSync/common/sessionSync'; +import { ChangeType, FileType, IEditSessionsLogService, ISessionSyncWorkbenchService } from 'vs/workbench/contrib/sessionSync/common/sessionSync'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -52,7 +52,7 @@ suite('Edit session sync', () => { fileService.registerProvider(Schemas.file, fileSystemProvider); // Stub out all services - instantiationService.stub(ILogService, logService); + instantiationService.stub(IEditSessionsLogService, logService); instantiationService.stub(IFileService, fileService); instantiationService.stub(INotificationService, new TestNotificationService()); instantiationService.stub(ISessionSyncWorkbenchService, new class extends mock() { }); -- cgit v1.2.3 From a08b7bb4d7f0427d515963d71beb50fc493cc0da Mon Sep 17 00:00:00 2001 From: Tomer Chachamu Date: Tue, 5 Jul 2022 22:53:02 +0100 Subject: Fix test error not showing when expanded (#153994) --- .../contrib/testing/browser/explorerProjections/nodeHelper.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts index 0f0d26be6b4..6a74365ede4 100644 --- a/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts +++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts @@ -6,11 +6,11 @@ import { IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; -import { IActionableTestTreeElement, TestExplorerTreeElement, TestItemTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/index'; +import { IActionableTestTreeElement, TestExplorerTreeElement, TestItemTreeElement, TestTreeErrorMessage } from 'vs/workbench/contrib/testing/browser/explorerProjections/index'; -export const testIdentityProvider: IIdentityProvider = { +export const testIdentityProvider: IIdentityProvider = { getId(element) { - return element.treeId + '\0' + element.test.expand; + return element.treeId + '\0' + (element instanceof TestTreeErrorMessage ? 'error' : element.test.expand); } }; -- cgit v1.2.3 From 3862aa876e95b4fe76922349e8c440092dc4bf6c Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 5 Jul 2022 14:57:52 -0700 Subject: Debt - clean up edit session action option declaration (#154202) Debt - clean up action option declaration --- .../browser/sessionSync.contribution.ts | 46 ++++++++-------------- 1 file changed, 17 insertions(+), 29 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts index 642ab0ce273..a4cf8be1f5f 100644 --- a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts +++ b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts @@ -7,7 +7,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Action2, IAction2Options, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { ISessionSyncWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EditSessionSchemaVersion, IEditSessionsLogService } from 'vs/workbench/contrib/sessionSync/common/sessionSync'; @@ -43,23 +43,17 @@ import { EditSessionsLogService } from 'vs/workbench/contrib/sessionSync/common/ registerSingleton(IEditSessionsLogService, EditSessionsLogService); registerSingleton(ISessionSyncWorkbenchService, SessionSyncWorkbenchService); -const resumeLatestCommand = { - id: 'workbench.experimental.editSessions.actions.resumeLatest', - title: { value: localize('resume latest', "Resume Latest Edit Session"), original: 'Resume Latest Edit Session' }, - category: EDIT_SESSION_SYNC_CATEGORY, -}; -const storeCurrentCommand = { - id: 'workbench.experimental.editSessions.actions.storeCurrent', - title: { value: localize('store current', "Store Current Edit Session"), original: 'Store Current Edit Session' }, - category: EDIT_SESSION_SYNC_CATEGORY, -}; -const continueEditSessionCommand = { +const continueEditSessionCommand: IAction2Options = { id: '_workbench.experimental.editSessions.actions.continueEditSession', title: { value: localize('continue edit session', "Continue Edit Session..."), original: 'Continue Edit Session...' }, + category: EDIT_SESSION_SYNC_CATEGORY, + f1: true }; -const openLocalFolderCommand = { +const openLocalFolderCommand: IAction2Options = { id: '_workbench.experimental.editSessions.actions.continueEditSession.openLocalFolder', title: { value: localize('continue edit session in local folder', "Open In Local Folder"), original: 'Open In Local Folder' }, + category: EDIT_SESSION_SYNC_CATEGORY, + precondition: IsWebContext }; const queryParamName = 'editSessionId'; const experimentalSettingName = 'workbench.experimental.editSessions.enabled'; @@ -150,10 +144,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon const that = this; this._register(registerAction2(class ContinueEditSessionAction extends Action2 { constructor() { - super({ - ...continueEditSessionCommand, - f1: true - }); + super(continueEditSessionCommand); } async run(accessor: ServicesAccessor, workspaceUri: URI | undefined): Promise { @@ -185,10 +176,10 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon this._register(registerAction2(class ApplyLatestEditSessionAction extends Action2 { constructor() { super({ - ...resumeLatestCommand, - menu: { - id: MenuId.CommandPalette, - } + id: 'workbench.experimental.editSessions.actions.resumeLatest', + title: { value: localize('resume latest.v2', "Resume Latest Edit Session"), original: 'Resume Latest Edit Session' }, + category: EDIT_SESSION_SYNC_CATEGORY, + f1: true, }); } @@ -206,10 +197,10 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon this._register(registerAction2(class StoreLatestEditSessionAction extends Action2 { constructor() { super({ - ...storeCurrentCommand, - menu: { - id: MenuId.CommandPalette, - } + id: 'workbench.experimental.editSessions.actions.storeCurrent', + title: { value: localize('store current.v2', "Store Current Edit Session"), original: 'Store Current Edit Session' }, + category: EDIT_SESSION_SYNC_CATEGORY, + f1: true, }); } @@ -400,10 +391,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon const that = this; this._register(registerAction2(class ContinueInLocalFolderAction extends Action2 { constructor() { - super({ - ...openLocalFolderCommand, - precondition: IsWebContext - }); + super(openLocalFolderCommand); } async run(accessor: ServicesAccessor): Promise { -- cgit v1.2.3 From 9c5408c04a614e641d7a69680686cb9346e0d7a8 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Tue, 5 Jul 2022 20:39:22 -0400 Subject: Detect terminal links with space, then line:col (#153957) --- .../contrib/terminal/browser/links/terminalLocalLinkDetector.ts | 1 + .../terminal/test/browser/links/terminalLocalLinkDetector.test.ts | 1 + 2 files changed, 2 insertions(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts index 19cab952add..cec80cf90b1 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts @@ -49,6 +49,7 @@ export const winLocalLinkClause = '((' + winPathPrefix + '|(' + winExcludedPathC /** As xterm reads from DOM, space in that case is nonbreaking char ASCII code - 160, replacing space with nonBreakningSpace or space ASCII code - 32. */ export const lineAndColumnClause = [ + '(([^:\\s\\(\\)<>\'\"\\[\\]]*) ((\\d+))(:(\\d+)))', // (file path) 336:9 [see #140780] '((\\S*)[\'"], line ((\\d+)( column (\\d+))?))', // "(file path)", line 45 [see #40468] '((\\S*)[\'"],((\\d+)(:(\\d+))?))', // "(file path)",45 [see #78205] '((\\S*) on line ((\\d+)(, column (\\d+))?))', // (file path) on line 8, column 13 diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts index 9f72ece8c18..93ff1ce3687 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts @@ -68,6 +68,7 @@ const supportedLinkFormats: LinkFormatInfo[] = [ { urlFormat: '{0} ({1}, {2})', line: '5', column: '3' }, { urlFormat: '{0}:{1}', line: '5' }, { urlFormat: '{0}:{1}:{2}', line: '5', column: '3' }, + { urlFormat: '{0} {1}:{2}', line: '5', column: '3' }, { urlFormat: '{0}[{1}]', line: '5' }, { urlFormat: '{0} [{1}]', line: '5' }, { urlFormat: '{0}[{1},{2}]', line: '5', column: '3' }, -- cgit v1.2.3 From c7e5301345e3d976f95f231a821e9e67555829cc Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 5 Jul 2022 22:54:58 -0700 Subject: fix #151986. Fix interactive window navigation. (#154200) fix #151986 --- .../browser/interactive.contribution.ts | 28 +++++++++++++++++----- .../interactive/browser/interactiveEditor.ts | 7 +++++- 2 files changed, 28 insertions(+), 7 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index f108213e01b..c49ec5ad519 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -9,6 +9,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { parse } from 'vs/base/common/marshalling'; import { Schemas } from 'vs/base/common/network'; +import { extname } from 'vs/base/common/resources'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -25,7 +26,7 @@ import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { EditorActivation } from 'vs/platform/editor/common/editor'; +import { EditorActivation, IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -48,9 +49,10 @@ import { InteractiveEditor } from 'vs/workbench/contrib/interactive/browser/inte import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput'; import { IInteractiveHistoryService, InteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService'; import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; +import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { CellEditType, CellKind, ICellOutput, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, CellUri, ICellOutput, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookContentProvider, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn'; @@ -214,12 +216,26 @@ export class InteractiveDocumentContribution extends Disposable implements IWork priority: RegisteredEditorPriority.exclusive }, { - canSupportResource: uri => uri.scheme === Schemas.vscodeInteractive, + canSupportResource: uri => uri.scheme === Schemas.vscodeInteractive || (uri.scheme === Schemas.vscodeNotebookCell && extname(uri) === '.interactive'), singlePerResource: true }, - ({ resource }) => { - const editorInput = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editor => editor.editor instanceof InteractiveEditorInput && editor.editor.resource?.toString() === resource.toString()); - return editorInput!; + ({ resource, options }) => { + const data = CellUri.parse(resource); + let notebookUri: URI = resource; + let cellOptions: IResourceEditorInput | undefined; + + if (data) { + notebookUri = data.notebook; + cellOptions = { resource, options }; + } + + const notebookOptions = { ...options, cellOptions } as INotebookEditorOptions; + + const editorInput = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editor => editor.editor instanceof InteractiveEditorInput && editor.editor.resource?.toString() === notebookUri.toString()); + return { + editor: editorInput!.editor, + options: notebookOptions + }; } ); } diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 37d2b4fde50..ca1f164befa 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -22,7 +22,7 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { EditorPaneSelectionChangeReason, IEditorMemento, IEditorOpenContext, IEditorPaneSelectionChangeEvent } from 'vs/workbench/common/editor'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput'; -import { ICellViewModel, INotebookEditorViewState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICellViewModel, INotebookEditorOptions, INotebookEditorViewState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { cellEditorBackground, NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; @@ -501,6 +501,11 @@ export class InteractiveEditor extends EditorPane { this.#syncWithKernel(); } + override setOptions(options: INotebookEditorOptions | undefined): void { + this.#notebookWidget.value?.setOptions(options); + super.setOptions(options); + } + #toEditorPaneSelectionChangeReason(e: ICursorPositionChangedEvent): EditorPaneSelectionChangeReason { switch (e.source) { case TextEditorSelectionSource.PROGRAMMATIC: return EditorPaneSelectionChangeReason.PROGRAMMATIC; -- cgit v1.2.3 From 6770e54beaade2921f576d953482f38999240d07 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 6 Jul 2022 10:09:43 +0200 Subject: add `ICodeEditorService#registerCodeEditorOpenHandler` so that 3rd parties can influence opening, e.g diff or merge editor This allows to remove editor registration for 3wm editor. --- .../browser/mergeEditor.contribution.ts | 11 ++- .../mergeEditor/browser/view/mergeEditor.ts | 85 ++++++++++------------ 2 files changed, 49 insertions(+), 47 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index 8dc07e6463f..09206f43520 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -8,11 +8,13 @@ import { registerAction2 } from 'vs/platform/actions/common/actions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; -import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenMergeEditor, ToggleActiveConflictInput1, ToggleActiveConflictInput2, SetColumnLayout, SetMixedLayout, OpenBaseFile } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands'; +import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenBaseFile, OpenMergeEditor, SetColumnLayout, SetMixedLayout, ToggleActiveConflictInput1, ToggleActiveConflictInput2 } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands'; import { MergeEditorCopyContentsToJSON, MergeEditorOpenContents } from 'vs/workbench/contrib/mergeEditor/browser/commands/devCommands'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; -import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; +import { MergeEditor, MergeEditorOpenHandlerContribution } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { MergeEditorSerializer } from './mergeEditorSerializer'; Registry.as(EditorExtensions.EditorPane).registerEditorPane( @@ -47,3 +49,8 @@ registerAction2(ToggleActiveConflictInput2); registerAction2(CompareInput1WithBaseCommand); registerAction2(CompareInput2WithBaseCommand); + + +Registry + .as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(MergeEditorOpenHandlerContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 544e07332cb..ff44bc01163 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -11,12 +11,13 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Color } from 'vs/base/common/color'; import { BugIndicatingError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { autorunWithStore, IObservable } from 'vs/base/common/observable'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/mergeEditor'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'; @@ -26,7 +27,7 @@ import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/men import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorOptions, ITextEditorOptions, ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -35,7 +36,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { AbstractTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; -import { EditorInputWithOptions, EditorResourceAccessor, IEditorOpenContext } from 'vs/workbench/common/editor'; +import { IEditorOpenContext } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; @@ -46,7 +47,6 @@ import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/v import { ctxBaseResourceScheme, ctxIsMergeEditor, ctxMergeEditorLayout, MergeEditorLayoutTypes } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import './colors'; import { InputCodeEditorView } from './editors/inputCodeEditorView'; @@ -86,9 +86,9 @@ export class MergeEditor extends AbstractTextEditor { private readonly _sessionDisposables = new DisposableStore(); private _grid!: Grid; - private readonly input1View = this._register(this.instantiation.createInstance(InputCodeEditorView, 1)); - private readonly input2View = this._register(this.instantiation.createInstance(InputCodeEditorView, 2)); - private readonly inputResultView = this._register(this.instantiation.createInstance(ResultCodeEditorView)); + private readonly input1View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 1)); + private readonly input2View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 2)); + private readonly inputResultView = this._register(this.instantiationService.createInstance(ResultCodeEditorView)); private readonly _layoutMode: MergeEditorLayout; private readonly _ctxIsMergeEditor: IContextKey; @@ -103,7 +103,7 @@ export class MergeEditor extends AbstractTextEditor { } constructor( - @IInstantiationService private readonly instantiation: IInstantiationService, + @IInstantiationService instantiation: IInstantiationService, @ILabelService private readonly _labelService: ILabelService, @IMenuService private readonly _menuService: IMenuService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -115,7 +115,6 @@ export class MergeEditor extends AbstractTextEditor { @IEditorService editorService: IEditorService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IFileService fileService: IFileService, - @IEditorResolverService private readonly _editorResolverService: IEditorResolverService, ) { super(MergeEditor.ID, telemetryService, instantiation, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService, fileService); @@ -186,7 +185,7 @@ export class MergeEditor extends AbstractTextEditor { createAndFillInActionBarActions(toolbarMenu, { renderShortTitle: true, shouldForwardArgs: true }, actions); if (actions.length > 0) { const [first] = actions; - const acceptBtn = this.instantiation.createInstance(FloatingClickWidget, this.inputResultView.editor, first.label, first.id); + const acceptBtn = this.instantiationService.createInstance(FloatingClickWidget, this.inputResultView.editor, first.label, first.id); toolbarMenuDisposables.add(acceptBtn.onClick(() => first.run(this.inputResultView.editor.getModel()?.uri))); toolbarMenuDisposables.add(acceptBtn); acceptBtn.render(); @@ -296,7 +295,6 @@ export class MergeEditor extends AbstractTextEditor { await super.setInput(input, options, context, token); this._sessionDisposables.clear(); - this._toggleEditorOverwrite(true); const model = await input.resolve(); this._model = model; @@ -373,7 +371,6 @@ export class MergeEditor extends AbstractTextEditor { super.clearInput(); this._sessionDisposables.clear(); - this._toggleEditorOverwrite(false); for (const { editor } of [this.input1View, this.input2View, this.inputResultView]) { editor.setModel(null); @@ -405,39 +402,6 @@ export class MergeEditor extends AbstractTextEditor { } this._ctxIsMergeEditor.set(visible); - this._toggleEditorOverwrite(visible); - } - - private readonly _editorOverrideHandle = this._store.add(new MutableDisposable()); - - private _toggleEditorOverwrite(haveIt: boolean) { - if (!haveIt) { - this._editorOverrideHandle.clear(); - return; - } - // this is RATHER UGLY. I dynamically register an editor for THIS (editor,input) so that - // navigating within the merge editor works, e.g navigating from the outline or breakcrumps - // or revealing a definition, reference etc - // TODO@jrieken @bpasero @lramos15 - const input = this.input; - if (input instanceof MergeEditorInput) { - this._editorOverrideHandle.value = this._editorResolverService.registerEditor( - `${input.result.scheme}:${input.result.fsPath}`, - { - id: `${this.getId()}/fake`, - label: this.input?.getName()!, - priority: RegisteredEditorPriority.exclusive - }, - {}, - (candidate): EditorInputWithOptions => { - const resource = EditorResourceAccessor.getCanonicalUri(candidate); - if (!isEqual(resource, this.model?.result.uri)) { - throw new Error(`Expected to be called WITH ${input.result.toString()}`); - } - return { editor: input }; - } - ); - } } // ---- interact with "outside world" via`getControl`, `scopedContextKeyService`: we only expose the result-editor keep the others internal @@ -506,6 +470,37 @@ export class MergeEditor extends AbstractTextEditor { } } +export class MergeEditorOpenHandlerContribution extends Disposable { + + constructor( + @IEditorService private readonly _editorService: IEditorService, + @ICodeEditorService codeEditorService: ICodeEditorService, + ) { + super(); + this._store.add(codeEditorService.registerCodeEditorOpenHandler(this.openCodeEditorFromMergeEditor.bind(this))); + } + + private async openCodeEditorFromMergeEditor(input: ITextResourceEditorInput, _source: ICodeEditor | null, sideBySide?: boolean | undefined): Promise { + const activePane = this._editorService.activeEditorPane; + if (!sideBySide + && input.options + && activePane instanceof MergeEditor + && activePane.getControl() + && activePane.input instanceof MergeEditorInput + && isEqual(input.resource, activePane.input.result) + ) { + // Special: stay inside the merge editor when it is active and when the input + // targets the result editor of the merge editor. + const targetEditor = activePane.getControl()!; + applyTextEditorOptions(input.options, targetEditor, ScrollType.Smooth); + return targetEditor; + } + + // cannot handle this + return null; + } +} + type IMergeEditorViewState = ICodeEditorViewState & { readonly input1State?: ICodeEditorViewState; readonly input2State?: ICodeEditorViewState; -- cgit v1.2.3 From 7c346e113b4cff07019139de115cc72005819383 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 6 Jul 2022 10:10:21 +0200 Subject: make sure document outline uses the code editor service from the current (code) editor --- .../contrib/codeEditor/browser/outline/documentSymbolsOutline.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts index 922bf591bf7..5f44dc67e26 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts @@ -403,8 +403,7 @@ class DocumentSymbolsOutlineCreator implements IOutlineCreator void; constructor( - @IOutlineService outlineService: IOutlineService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IOutlineService outlineService: IOutlineService ) { const reg = outlineService.registerOutlineCreator(this); this.dispose = () => reg.dispose(); @@ -427,7 +426,7 @@ class DocumentSymbolsOutlineCreator implements IOutlineCreator accessor.get(IInstantiationService).createInstance(DocumentSymbolsOutline, editor!, target, firstLoadBarrier)); await firstLoadBarrier.wait(); return result; } -- cgit v1.2.3 From b3d6ef63a72e5f388a86265aae79c238ffd1f4ee Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 6 Jul 2022 10:58:19 +0200 Subject: show error when action fails --- .../contrib/userDataProfile/common/userDataProfileActions.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts index cda5ca4d8fe..01a3ef3d346 100644 --- a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts +++ b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts @@ -117,12 +117,17 @@ registerAction2(class RemoveProfileAction extends Action2 { const userDataProfileService = accessor.get(IUserDataProfileService); const userDataProfilesService = accessor.get(IUserDataProfilesService); const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService); + const notificationService = accessor.get(INotificationService); const profiles = userDataProfilesService.profiles.filter(p => p.id !== userDataProfileService.currentProfile.id && !p.isDefault); if (profiles.length) { const pick = await quickInputService.pick(profiles.map(profile => ({ label: profile.name, profile })), { placeHolder: localize('pick profile', "Select Settings Profile") }); if (pick) { - await userDataProfileManagementService.removeProfile(pick.profile); + try { + await userDataProfileManagementService.removeProfile(pick.profile); + } catch (error) { + notificationService.error(error); + } } } } -- cgit v1.2.3 From b94a4bf43814fb4582a5f5e5cf8c48ecd2b90766 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 6 Jul 2022 13:19:07 +0200 Subject: always include default profile in the profiles --- src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 8a75af540d3..44aa262be18 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -133,7 +133,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements private profileStatusAccessor: IStatusbarEntryAccessor | undefined; private updateStatus(): void { - if (this.userDataProfilesService.profiles.length) { + if (this.userDataProfilesService.profiles.length > 1) { const statusBarEntry: IStatusbarEntry = { name: PROFILES_CATEGORY, command: 'workbench.profiles.actions.switchProfile', -- cgit v1.2.3 From b1eab983e40dfdca90d0f9833b00afe4233c12c3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 6 Jul 2022 13:41:24 +0200 Subject: remove `forEach` usage in my and some other places (#154252) https://github.com/microsoft/vscode/issues/154195 --- src/vs/workbench/contrib/snippets/browser/snippetsFile.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts index eb1497201da..784296b70de 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { parse as jsonParse, getNodeType } from 'vs/base/common/json'; -import { forEach } from 'vs/base/common/collections'; import { localize } from 'vs/nls'; import { extname, basename } from 'vs/base/common/path'; import { SnippetParser, Variable, Placeholder, Text } from 'vs/editor/contrib/snippet/browser/snippetParser'; @@ -256,17 +255,15 @@ export class SnippetFile { this._loadPromise = Promise.resolve(this._load()).then(content => { const data = jsonParse(content); if (getNodeType(data) === 'object') { - forEach(data, entry => { - const { key: name, value: scopeOrTemplate } = entry; + for (const [name, scopeOrTemplate] of Object.entries(data)) { if (isJsonSerializedSnippet(scopeOrTemplate)) { this._parseSnippet(name, scopeOrTemplate, this.data); } else { - forEach(scopeOrTemplate, entry => { - const { key: name, value: template } = entry; + for (const [name, template] of Object.entries(scopeOrTemplate)) { this._parseSnippet(name, template, this.data); - }); + } } - }); + } } return this; }); -- cgit v1.2.3 From d45ad24af8762cb434a8b5d6094b277efc62e8a3 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 6 Jul 2022 13:55:39 +0200 Subject: remove totally unneeded hack that slipped through... (#154254) --- src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts index b07897a513a..6906a0b4fb7 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts @@ -116,7 +116,7 @@ class ModelEditTask implements IDisposable { if (!edit.text) { return edit; } - const text = new SnippetParser().parse(edit.text, false, false).toString(); + const text = new SnippetParser().text(edit.text); return { ...edit, insertAsSnippet: false, text }; } } @@ -233,13 +233,13 @@ export class BulkTextEdits { let makeMinimal = false; if (this._editor?.getModel()?.uri.toString() === ref.object.textEditorModel.uri.toString()) { task = new EditorEditTask(ref, this._editor); - makeMinimal = true && false; // todo@jrieken HACK + makeMinimal = true; } else { task = new ModelEditTask(ref); } for (const edit of value) { - if (makeMinimal) { + if (makeMinimal && !edit.textEdit.insertAsSnippet) { const newEdits = await this._editorWorker.computeMoreMinimalEdits(edit.resource, [edit.textEdit]); if (!newEdits) { task.addEdit(edit); -- cgit v1.2.3 From 5e1a19f4d439cd4a0ac23e0169df94a5646aa0a2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 05:50:54 -0700 Subject: Ensure exact match links are only checked on absolute paths Fixes #153832 --- src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts index bfc044ba133..0d07ef46dfd 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts @@ -218,6 +218,9 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener { private async _tryOpenExactLink(text: string, link: ITerminalSimpleLink): Promise { const sanitizedLink = text.replace(/:\d+(:\d+)?$/, ''); + if (!osPathModule(this._os).isAbsolute(sanitizedLink)) { + return false; + } try { const result = await this._getExactMatch(sanitizedLink); if (result) { -- cgit v1.2.3 From 6df57ad27f21eae9aedb2316c96cb7c07632f1cb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 06:02:39 -0700 Subject: Fix test and only avoid exact match when a separator does not exist --- .../terminal/browser/links/terminalLinkOpeners.ts | 8 +++- .../test/browser/links/terminalLinkOpeners.test.ts | 51 +++++++++++++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts index 0d07ef46dfd..fda9a67bd29 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts @@ -218,7 +218,13 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener { private async _tryOpenExactLink(text: string, link: ITerminalSimpleLink): Promise { const sanitizedLink = text.replace(/:\d+(:\d+)?$/, ''); - if (!osPathModule(this._os).isAbsolute(sanitizedLink)) { + // For only file links disallow exact link matching, for example searching for `foo.txt` + // when no cwd information is available should search when only the initial cwd is available + // as it's ambiguous if there are multiple matches. + // + // However, for `src/foo.txt`, if there's an exact match for `src/foo.txt` in any folder we + // want to take it, even if there are partial matches like `src2/foo.txt` available. + if (!sanitizedLink.match(/[\\/]/)) { return false; } try { diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts index dcbdef59e2c..e7eea1e62fc 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts @@ -97,10 +97,21 @@ suite('Workbench - TerminalLinkOpeners', () => { capabilities.add(TerminalCapability.CommandDetection, commandDetection); }); - test('should open single exact match against cwd when searching if it exists', async () => { + test('should open single exact match against cwd when searching if it exists when command detection cwd is available', async () => { localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux); const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener); opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/initial/cwd'), localFileOpener, localFolderOpener, OperatingSystem.Linux); + // Set a fake detected command starting as line 0 to establish the cwd + commandDetection.setCommands([{ + command: '', + cwd: '/initial/cwd', + timestamp: 0, + getOutput() { return undefined; }, + marker: { + line: 0 + } as Partial as any, + hasOutput: true + }]); fileService.setFiles([ URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo/bar.txt' }), URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo2/bar.txt' }) @@ -116,6 +127,44 @@ suite('Workbench - TerminalLinkOpeners', () => { }); }); + test('should open single exact match against cwd for paths containing a separator when searching if it exists, even when command detection isn\'t available', async () => { + localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux); + const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener); + opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/initial/cwd'), localFileOpener, localFolderOpener, OperatingSystem.Linux); + fileService.setFiles([ + URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo/bar.txt' }), + URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo2/bar.txt' }) + ]); + await opener.open({ + text: 'foo/bar.txt', + bufferRange: { start: { x: 1, y: 1 }, end: { x: 8, y: 1 } }, + type: TerminalBuiltinLinkType.Search + }); + deepStrictEqual(activationResult, { + link: 'file:///initial/cwd/foo/bar.txt', + source: 'editor' + }); + }); + + test('should not open single exact match for paths not containing a when command detection isn\'t available', async () => { + localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux); + const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener); + opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/initial/cwd'), localFileOpener, localFolderOpener, OperatingSystem.Linux); + fileService.setFiles([ + URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo/bar.txt' }), + URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo2/bar.txt' }) + ]); + await opener.open({ + text: 'bar.txt', + bufferRange: { start: { x: 1, y: 1 }, end: { x: 8, y: 1 } }, + type: TerminalBuiltinLinkType.Search + }); + deepStrictEqual(activationResult, { + link: 'bar.txt', + source: 'search' + }); + }); + suite('macOS/Linux', () => { setup(() => { localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux); -- cgit v1.2.3 From 585b6685dda19344ec0304331f294f1c5f9efe24 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 6 Jul 2022 15:08:38 +0200 Subject: remove forEach usage (#154259) * remove forEach usage * remove extra line --- .../contrib/extensions/browser/fileBasedRecommendations.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index d1b91197ff4..57b4387c1c7 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -14,7 +14,7 @@ import { localize } from 'vs/nls'; import { StorageScope, IStorageService, StorageTarget } from 'vs/platform/storage/common/storage'; import { IProductService } from 'vs/platform/product/common/productService'; import { ImportantExtensionTip } from 'vs/base/common/product'; -import { forEach, IStringDictionary } from 'vs/base/common/collections'; +import { IStringDictionary } from 'vs/base/common/collections'; import { ITextModel } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; import { basename, extname } from 'vs/base/common/resources'; @@ -115,10 +115,10 @@ export class FileBasedRecommendations extends ExtensionRecommendations { this.tasExperimentService = tasExperimentService; if (productService.extensionTips) { - forEach(productService.extensionTips, ({ key, value }) => this.extensionTips.set(key.toLowerCase(), value)); + Object.entries(productService.extensionTips).forEach(([key, value]) => this.extensionTips.set(key.toLowerCase(), value)); } if (productService.extensionImportantTips) { - forEach(productService.extensionImportantTips, ({ key, value }) => this.importantExtensionTips.set(key.toLowerCase(), value)); + Object.entries(productService.extensionImportantTips).forEach(([key, value]) => this.importantExtensionTips.set(key.toLowerCase(), value)); } } @@ -153,7 +153,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations { const cachedRecommendations = this.getCachedRecommendations(); const now = Date.now(); // Retire existing recommendations if they are older than a week or are not part of this.productService.extensionTips anymore - forEach(cachedRecommendations, ({ key, value }) => { + Object.entries(cachedRecommendations).forEach(([key, value]) => { const diff = (now - value) / milliSecondsInADay; if (diff <= 7 && allRecommendations.indexOf(key) > -1) { this.fileBasedRecommendations.set(key.toLowerCase(), { recommendedTime: value }); @@ -400,7 +400,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations { storedRecommendations = storedRecommendations.reduce((result, id) => { result[id] = Date.now(); return result; }, >{}); } const result: IStringDictionary = {}; - forEach(storedRecommendations, ({ key, value }) => { + Object.entries(storedRecommendations).forEach(([key, value]) => { if (typeof value === 'number') { result[key.toLowerCase()] = value; } -- cgit v1.2.3 From d89ea128eca64497fe46bfb36891a8b421f31c30 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 06:39:41 -0700 Subject: Fix remote Windows terminal link URI Fixes #154265 Fixes #144534 --- .../terminal/browser/links/terminalLinkOpeners.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts index bfc044ba133..52951ddd36c 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts @@ -189,9 +189,23 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener { // Try open as an absolute link let resourceMatch: IResourceMatch | undefined; if (absolutePath) { - const slashNormalizedPath = this._os === OperatingSystem.Windows ? absolutePath.replace(/\\/g, '/') : absolutePath; - const scheme = this._workbenchEnvironmentService.remoteAuthority ? Schemas.vscodeRemote : Schemas.file; - const uri = URI.from({ scheme, path: slashNormalizedPath }); + let normalizedAbsolutePath: string = absolutePath; + if (this._os === OperatingSystem.Windows) { + normalizedAbsolutePath = absolutePath.replace(/\\/g, '/'); + if (normalizedAbsolutePath.match(/[a-z]:/i)) { + normalizedAbsolutePath = `/${normalizedAbsolutePath}`; + } + } + let uri: URI; + if (this._workbenchEnvironmentService.remoteAuthority) { + uri = URI.from({ + scheme: Schemas.vscodeRemote, + authority: this._workbenchEnvironmentService.remoteAuthority, + path: normalizedAbsolutePath + }); + } else { + uri = URI.file(normalizedAbsolutePath); + } try { const fileStat = await this._fileService.stat(uri); resourceMatch = { uri, isDirectory: fileStat.isDirectory }; -- cgit v1.2.3 From 94e4ee8e4902b332aa4290adfdb806a8414b6065 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 07:05:27 -0700 Subject: Prevent unicode11 addon from loading in unit tests It's not clear what caused the flake in #153757 but this should fix the suspicious unicode warning. We can reinvestigate if it happens again after this fix. Fixes #153757 --- .../workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts index a35b293a2a9..99bc8e0b095 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts @@ -80,7 +80,7 @@ const defaultTerminalConfig: Partial = { scrollback: 1000, fastScrollSensitivity: 2, mouseWheelScrollSensitivity: 1, - unicodeVersion: '11' + unicodeVersion: '6' }; suite('XtermTerminal', () => { -- cgit v1.2.3 From 2e0e882ac5997526583fd4e9e204ee77bdd534d8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 07:37:31 -0700 Subject: Improve comment --- .../contrib/terminal/browser/links/terminalLinkOpeners.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts index fda9a67bd29..3041efab208 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts @@ -218,12 +218,13 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener { private async _tryOpenExactLink(text: string, link: ITerminalSimpleLink): Promise { const sanitizedLink = text.replace(/:\d+(:\d+)?$/, ''); - // For only file links disallow exact link matching, for example searching for `foo.txt` - // when no cwd information is available should search when only the initial cwd is available - // as it's ambiguous if there are multiple matches. + // For links made up of only a file name (no folder), disallow exact link matching. For + // example searching for `foo.txt` when there is no cwd information available (ie. only the + // initial cwd) should NOT search as it's ambiguous if there are multiple matches. // - // However, for `src/foo.txt`, if there's an exact match for `src/foo.txt` in any folder we - // want to take it, even if there are partial matches like `src2/foo.txt` available. + // However, for a link like `src/foo.txt`, if there's an exact match for `src/foo.txt` in + // any folder we want to take it, even if there are partial matches like `src2/foo.txt` + // available. if (!sanitizedLink.match(/[\\/]/)) { return false; } -- cgit v1.2.3 From f9f353c90becae9610fc28f5b47078ce859eaa00 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 6 Jul 2022 08:53:12 -0700 Subject: support vscode.dev link generation in notebook editor (#154183) * support vscode.dev link generation in notebook editor * Update comments. --- .../workbench/contrib/notebook/browser/controller/coreActions.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts index f7b01782aa0..9a42a89b378 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts @@ -42,7 +42,8 @@ export const enum CellToolbarOrder { export const enum CellOverflowToolbarGroups { Copy = '1_copy', Insert = '2_insert', - Edit = '3_edit' + Edit = '3_edit', + Share = '4_share' } export interface INotebookActionContext { @@ -427,3 +428,9 @@ MenuRegistry.appendMenuItem(MenuId.EditorContext, { group: CellOverflowToolbarGroups.Insert, when: NOTEBOOK_EDITOR_FOCUSED }); + +MenuRegistry.appendMenuItem(MenuId.NotebookCellTitle, { + title: localize('miShare', "Share"), + submenu: MenuId.EditorContextShare, + group: CellOverflowToolbarGroups.Share +}); -- cgit v1.2.3 From 0df86c37b602d08bab3e8def45dd445d36f55fc2 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 6 Jul 2022 11:54:37 -0400 Subject: add `hide` property to configure which tasks appear in the `Tasks: run task` quickpick (#154166) --- .../contrib/tasks/browser/abstractTaskService.ts | 2 +- .../contrib/tasks/browser/taskQuickPick.ts | 15 ++++++++++----- .../contrib/tasks/browser/terminalTaskSystem.ts | 22 ++++++++++++++++------ .../contrib/tasks/common/jsonSchema_v2.ts | 9 +++++++++ .../contrib/tasks/common/taskConfiguration.ts | 17 ++++++++++++----- src/vs/workbench/contrib/tasks/common/tasks.ts | 11 +++++++++++ 6 files changed, 59 insertions(+), 17 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 9ef8982eebe..da7e0dccf58 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -1588,7 +1588,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer { identifier: id, dependsOn: extensionTasks.map((extensionTask) => { return { uri: extensionTask.getWorkspaceFolder()!.uri, task: extensionTask._id }; }), - name: id, + name: id } ); return { task, resolver }; diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts index 77dc6f60769..a2cc123e381 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts @@ -24,7 +24,6 @@ import { TaskQuickPickEntryType } from 'vs/workbench/contrib/tasks/browser/abstr export const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail'; export const QUICKOPEN_SKIP_CONFIG = 'task.quickOpen.skip'; - export function isWorkspaceFolder(folder: IWorkspace | IWorkspaceFolder): folder is IWorkspaceFolder { return 'uri' in folder; } @@ -108,7 +107,9 @@ export class TaskQuickPick extends Disposable { groupLabel: string, extraButtons: IQuickInputButton[] = []) { entries.push({ type: 'separator', label: groupLabel }); tasks.forEach(task => { - entries.push(this._createTaskEntry(task, extraButtons)); + if (!task.configurationProperties.hide) { + entries.push(this._createTaskEntry(task, extraButtons)); + } }); } @@ -304,7 +305,7 @@ export class TaskQuickPick extends Disposable { private async _doPickerSecondLevel(picker: IQuickPick, type: string) { picker.busy = true; if (type === SHOW_ALL) { - const items = (await this._taskService.tasks()).sort((a, b) => this._sorter.compare(a, b)).map(task => this._createTaskEntry(task)); + const items = (await this._taskService.tasks()).filter(t => !t.configurationProperties.hide).sort((a, b) => this._sorter.compare(a, b)).map(task => this._createTaskEntry(task)); items.push(...TaskQuickPick.allSettingEntries(this._configurationService)); picker.items = items; } else { @@ -353,9 +354,13 @@ export class TaskQuickPick extends Disposable { private async _getEntriesForProvider(type: string): Promise[]> { const tasks = (await this._taskService.tasks({ type })).sort((a, b) => this._sorter.compare(a, b)); - let taskQuickPickEntries: QuickPickInput[]; + let taskQuickPickEntries: QuickPickInput[] = []; if (tasks.length > 0) { - taskQuickPickEntries = tasks.map(task => this._createTaskEntry(task)); + for (const task of tasks) { + if (!task.configurationProperties.hide) { + taskQuickPickEntries.push(this._createTaskEntry(task)); + } + } taskQuickPickEntries.push({ type: 'separator' }, { diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index d1dd693f370..0beaded25b4 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -516,12 +516,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { for (const dependency of task.configurationProperties.dependsOn) { const dependencyTask = await resolver.resolve(dependency.uri, dependency.task!); if (dependencyTask) { - if (dependencyTask.configurationProperties.icon) { - dependencyTask.configurationProperties.icon.id ||= task.configurationProperties.icon?.id; - dependencyTask.configurationProperties.icon.color ||= task.configurationProperties.icon?.color; - } else { - dependencyTask.configurationProperties.icon = task.configurationProperties.icon; - } + this._adoptConfigurationForDependencyTask(dependencyTask, task); const key = dependencyTask.getMapKey(); let promise = this._activeTasks[key] ? this._getDependencyPromise(this._activeTasks[key]) : undefined; if (!promise) { @@ -590,6 +585,21 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { }); } + private _adoptConfigurationForDependencyTask(dependencyTask: Task, task: Task): void { + if (dependencyTask.configurationProperties.icon) { + dependencyTask.configurationProperties.icon.id ||= task.configurationProperties.icon?.id; + dependencyTask.configurationProperties.icon.color ||= task.configurationProperties.icon?.color; + } else { + dependencyTask.configurationProperties.icon = task.configurationProperties.icon; + } + + if (dependencyTask.configurationProperties.hide) { + dependencyTask.configurationProperties.hide ||= task.configurationProperties.hide; + } else { + dependencyTask.configurationProperties.hide = task.configurationProperties.hide; + } + } + private async _getDependencyPromise(task: IActiveTerminalData): Promise { if (!task.task.configurationProperties.isBackground) { return task.promise; diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts index 077bd89a60e..fa67f8ecf65 100644 --- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts +++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts @@ -45,6 +45,13 @@ const shellCommand: IJSONSchema = { deprecationMessage: nls.localize('JsonSchema.tasks.isShellCommand.deprecated', 'The property isShellCommand is deprecated. Use the type property of the task and the shell property in the options instead. See also the 1.14 release notes.') }; + +const hide: IJSONSchema = { + type: 'boolean', + description: nls.localize('JsonSchema.hide', 'Hide this task from the run task quick pick'), + default: true +}; + const taskIdentifier: IJSONSchema = { type: 'object', additionalProperties: true, @@ -407,6 +414,7 @@ const taskConfiguration: IJSONSchema = { }, presentation: Objects.deepClone(presentation), icon: Objects.deepClone(icon), + hide: Objects.deepClone(hide), options: options, problemMatcher: { $ref: '#/definitions/problemMatcherType', @@ -479,6 +487,7 @@ taskDescriptionProperties.command = Objects.deepClone(command); taskDescriptionProperties.args = Objects.deepClone(args); taskDescriptionProperties.isShellCommand = Objects.deepClone(shellCommand); taskDescriptionProperties.dependsOn = dependsOn; +taskDescriptionProperties.hide = Objects.deepClone(hide); taskDescriptionProperties.dependsOrder = dependsOrder; taskDescriptionProperties.identifier = Objects.deepClone(identifier); taskDescriptionProperties.type = Objects.deepClone(taskType); diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts index c5d4058bcb0..0bb00f57620 100644 --- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts @@ -362,6 +362,11 @@ export interface IConfigurationProperties { * The icon's color in the terminal tabs list */ color?: string; + + /** + * Do not show this task in the run task quickpick + */ + hide?: boolean; } export interface ICustomTask extends ICommandProperties, IConfigurationProperties { @@ -1322,7 +1327,8 @@ namespace ConfigurationProperties { { property: 'presentation', type: CommandConfiguration.PresentationOptions }, { property: 'problemMatchers' }, { property: 'options' }, - { property: 'icon' } + { property: 'icon' }, + { property: 'hide' } ]; export function from(this: void, external: IConfigurationProperties & { [key: string]: any }, context: IParseContext, @@ -1350,7 +1356,7 @@ namespace ConfigurationProperties { result.identifier = external.identifier; } result.icon = external.icon; - + result.hide = external.hide; if (external.isBackground !== undefined) { result.isBackground = !!external.isBackground; } @@ -1483,7 +1489,7 @@ namespace ConfiguringTask { type, taskIdentifier, RunOptions.fromConfiguration(external.runOptions), - {} + { hide: external.hide } ); const configuration = ConfigurationProperties.from(external, context, true, source, typeDeclaration.properties); result.addTaskLoadMessages(configuration.errors); @@ -1635,7 +1641,8 @@ namespace CustomTask { { name: configuredProps.configurationProperties.name || contributedTask.configurationProperties.name, identifier: configuredProps.configurationProperties.identifier || contributedTask.configurationProperties.identifier, - icon: configuredProps.configurationProperties.icon + icon: configuredProps.configurationProperties.icon, + hide: configuredProps.configurationProperties.hide }, ); @@ -2119,7 +2126,7 @@ class ConfigurationParser { identifier: name, group: Tasks.TaskGroup.Build, isBackground: isBackground, - problemMatchers: matchers, + problemMatchers: matchers } ); const taskGroupKind = GroupKind.from(fileConfig.group); diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 1b52c33d02a..f4956cc9aef 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -549,6 +549,11 @@ export interface IConfigurationProperties { * The icon for this task in the terminal tabs list */ icon?: { id?: string; color?: string }; + + /** + * Do not show this task in the run task quickpick + */ + hide?: boolean; } export enum RunOnOptions { @@ -914,6 +919,11 @@ export class ContributedTask extends CommonTask { */ icon: { id?: string; color?: string } | undefined; + /** + * Don't show the task in the run task quickpick + */ + hide?: boolean; + public constructor(id: string, source: IExtensionTaskSource, label: string, type: string | undefined, defines: KeyedTaskIdentifier, command: ICommandConfiguration, hasDefinedMatchers: boolean, runOptions: IRunOptions, configurationProperties: IConfigurationProperties) { @@ -922,6 +932,7 @@ export class ContributedTask extends CommonTask { this.hasDefinedMatchers = hasDefinedMatchers; this.command = command; this.icon = configurationProperties.icon; + this.hide = configurationProperties.hide; } public override clone(): ContributedTask { -- cgit v1.2.3 From f413297170178f16ab218202153b629d59a98be1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 6 Jul 2022 18:33:04 +0200 Subject: joh/plastic fowl (#154275) * * derive workspace dto with util * be strict when defining reference version ids (must be set to a value or undefined) * relax `ResourceNotebookCellEdit` --- .../contrib/bulkEdit/browser/bulkCellEdits.ts | 26 +++++++++++++++++----- .../browser/viewModel/notebookViewModelImpl.ts | 7 +++--- .../contrib/notebook/common/notebookCommon.ts | 10 ++++++++- 3 files changed, 34 insertions(+), 9 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts index 1f533a82ccc..0e870b7b639 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts @@ -6,20 +6,36 @@ import { groupBy } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { compare } from 'vs/base/common/strings'; +import { isObject } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService'; import { WorkspaceEditMetadata } from 'vs/editor/common/languages'; import { IProgress } from 'vs/platform/progress/common/progress'; import { UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo'; -import { ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellPartialMetadataEdit, ICellReplaceEdit, IDocumentMetadataEdit, IWorkspaceNotebookCellEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; -export class ResourceNotebookCellEdit extends ResourceEdit { +export class ResourceNotebookCellEdit extends ResourceEdit implements IWorkspaceNotebookCellEdit { + + static is(candidate: any): candidate is IWorkspaceNotebookCellEdit { + if (candidate instanceof ResourceNotebookCellEdit) { + return true; + } + return URI.isUri((candidate).resource) + && isObject((candidate).cellEdit); + } + + static lift(edit: IWorkspaceNotebookCellEdit): ResourceNotebookCellEdit { + if (edit instanceof ResourceNotebookCellEdit) { + return edit; + } + return new ResourceNotebookCellEdit(edit.resource, edit.cellEdit, edit.notebookVersionId, edit.metadata); + } constructor( readonly resource: URI, - readonly cellEdit: ICellEditOperation, - readonly versionId?: number, + readonly cellEdit: ICellPartialMetadataEdit | IDocumentMetadataEdit | ICellReplaceEdit, + readonly notebookVersionId: number | undefined = undefined, metadata?: WorkspaceEditMetadata ) { super(metadata); @@ -49,7 +65,7 @@ export class BulkCellEdits { const ref = await this._notebookModelService.resolve(first.resource); // check state - if (typeof first.versionId === 'number' && ref.object.notebook.versionId !== first.versionId) { + if (typeof first.notebookVersionId === 'number' && ref.object.notebook.versionId !== first.notebookVersionId) { ref.dispose(); throw new Error(`Notebook '${first.resource}' has changed in the meantime`); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts index 951fbe0e2e2..a024bbe93a6 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts @@ -17,7 +17,7 @@ import { FindMatch, IModelDecorationOptions, IModelDeltaDecoration, TrackedRange import { MultiModelEditStackElement, SingleModelEditStackElement } from 'vs/editor/common/model/editStack'; import { IntervalNode, IntervalTree } from 'vs/editor/common/model/intervalTree'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { WorkspaceTextEdit } from 'vs/editor/common/languages'; +import { IWorkspaceTextEdit } from 'vs/editor/common/languages'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { FoldingRegions } from 'vs/editor/contrib/folding/browser/foldingRanges'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -924,14 +924,15 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD return; } - const textEdits: WorkspaceTextEdit[] = []; + const textEdits: IWorkspaceTextEdit[] = []; this._lastNotebookEditResource.push(matches[0].cell.uri); matches.forEach(match => { match.matches.forEach((singleMatch, index) => { if ((singleMatch as OutputFindMatch).index === undefined) { textEdits.push({ - edit: { range: (singleMatch as FindMatch).range, text: texts[index] }, + versionId: undefined, + textEdit: { range: (singleMatch as FindMatch).range, text: texts[index] }, resource: match.cell.uri }); } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index d0ccff06ccd..a95ecc6fb84 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -17,7 +17,7 @@ import { ISplice } from 'vs/base/common/sequence'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ILineChange } from 'vs/editor/common/diff/diffComputer'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { Command } from 'vs/editor/common/languages'; +import { Command, WorkspaceEditMetadata } from 'vs/editor/common/languages'; import { IReadonlyTextBuffer } from 'vs/editor/common/model'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -497,6 +497,14 @@ export interface ICellMoveEdit { export type IImmediateCellEditOperation = ICellOutputEditByHandle | ICellPartialMetadataEditByHandle | ICellOutputItemEdit | ICellPartialInternalMetadataEdit | ICellPartialInternalMetadataEditByHandle | ICellPartialMetadataEdit; export type ICellEditOperation = IImmediateCellEditOperation | ICellReplaceEdit | ICellOutputEdit | ICellMetadataEdit | ICellPartialMetadataEdit | ICellPartialInternalMetadataEdit | IDocumentMetadataEdit | ICellMoveEdit | ICellOutputItemEdit | ICellLanguageEdit; + +export interface IWorkspaceNotebookCellEdit { + metadata?: WorkspaceEditMetadata; + resource: URI; + notebookVersionId: number | undefined; + cellEdit: ICellPartialMetadataEdit | IDocumentMetadataEdit | ICellReplaceEdit; +} + export interface NotebookData { readonly cells: ICellDto2[]; readonly metadata: NotebookDocumentMetadata; -- cgit v1.2.3 From c133b130bfd88a134e1803d60e6ce5d094af6a03 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 6 Jul 2022 12:45:28 -0400 Subject: remove `forEach` for tasks (#154273) * part of #154195 * Update src/vs/workbench/api/browser/mainThreadTask.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> * Update src/vs/workbench/api/browser/mainThreadTask.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- .../contrib/tasks/browser/runAutomaticTasks.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index 3d91091af90..55d63c00125 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -8,7 +8,6 @@ import * as resources from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ITaskService, IWorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService'; -import { forEach } from 'vs/base/common/collections'; import { RunOnOptions, Task, TaskRunSource, TaskSource, TaskSourceKind, TASKS_CATEGORY, WorkspaceFileTaskSource, IWorkspaceTaskSource } from 'vs/workbench/contrib/tasks/common/tasks'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -106,22 +105,22 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut }); } if (resultElement.configurations) { - forEach(resultElement.configurations.byIdentifier, (configedTask) => { - if (configedTask.value.runOptions.runOn === RunOnOptions.folderOpen) { + for (const configuredTask of Object.values(resultElement.configurations.byIdentifier)) { + if (configuredTask.runOptions.runOn === RunOnOptions.folderOpen) { tasks.push(new Promise(resolve => { - taskService.getTask(resultElement.workspaceFolder, configedTask.value._id, true).then(task => resolve(task)); + taskService.getTask(resultElement.workspaceFolder, configuredTask._id, true).then(task => resolve(task)); })); - if (configedTask.value._label) { - taskNames.push(configedTask.value._label); + if (configuredTask._label) { + taskNames.push(configuredTask._label); } else { - taskNames.push(configedTask.value.configures.task); + taskNames.push(configuredTask.configures.task); } - const location = RunAutomaticTasks._getTaskSource(configedTask.value._source); + const location = RunAutomaticTasks._getTaskSource(configuredTask._source); if (location) { locations.set(location.fsPath, location); } } - }); + } } }); } -- cgit v1.2.3 From 04d9921d07e1e6145dbbed4303a33479a16d0558 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 10:38:38 -0700 Subject: Add separators to command decoration menu Part of #153382 --- .../terminal/browser/xterm/decorationAddon.ts | 28 +++++++++++++++------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index 0d033529f59..d3284f43a86 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -12,7 +12,7 @@ import { CommandInvalidationReason, ITerminalCapabilityStore, TerminalCapability import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, Separator } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { localize } from 'vs/nls'; @@ -353,24 +353,34 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { private async _getCommandActions(command: ITerminalCommand): Promise { const actions: IAction[] = []; + if (command.command !== '') { + const label = localize("terminal.rerunCommand", 'Rerun Command'); + actions.push({ + class: undefined, tooltip: label, dispose: () => { }, id: 'terminal.rerunCommand', label, enabled: true, + run: () => this._onDidRequestRunCommand.fire({ command }) + }); + } if (command.hasOutput) { + if (actions.length > 0) { + actions.push(new Separator()); + } + const labelText = localize("terminal.copyOutput", 'Copy Output'); actions.push({ - class: 'copy-output', tooltip: 'Copy Output', dispose: () => { }, id: 'terminal.copyOutput', label: localize("terminal.copyOutput", 'Copy Output'), enabled: true, + class: undefined, tooltip: labelText, dispose: () => { }, id: 'terminal.copyOutput', label: labelText, enabled: true, run: () => this._clipboardService.writeText(command.getOutput()!) }); + const labelHtml = localize("terminal.copyOutputAsHtml", 'Copy Output as HTML'); actions.push({ - class: 'copy-output', tooltip: 'Copy Output as HTML', dispose: () => { }, id: 'terminal.copyOutputAsHtml', label: localize("terminal.copyOutputAsHtml", 'Copy Output as HTML'), enabled: true, + class: undefined, tooltip: labelHtml, dispose: () => { }, id: 'terminal.copyOutputAsHtml', label: labelHtml, enabled: true, run: () => this._onDidRequestRunCommand.fire({ command, copyAsHtml: true }) }); } - if (command.command !== '') { - actions.push({ - class: 'rerun-command', tooltip: 'Rerun Command', dispose: () => { }, id: 'terminal.rerunCommand', label: localize("terminal.rerunCommand", 'Rerun Command'), enabled: true, - run: () => this._onDidRequestRunCommand.fire({ command }) - }); + if (actions.length > 0) { + actions.push(new Separator()); } + const label = localize("terminal.learnShellIntegration", 'Learn About Shell Integration'); actions.push({ - class: 'how-does-this-work', tooltip: 'How does this work?', dispose: () => { }, id: 'terminal.howDoesThisWork', label: localize("terminal.howDoesThisWork", 'How does this work?'), enabled: true, + class: undefined, tooltip: label, dispose: () => { }, id: 'terminal.learnShellIntegration', label, enabled: true, run: () => this._openerService.open('https://code.visualstudio.com/docs/editor/integrated-terminal#_shell-integration') }); return actions; -- cgit v1.2.3 From 891fa893646795ec4995dc29f7c8ad76efbca312 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 10:41:54 -0700 Subject: Add copy command to command decoration menu Fixes #153382 --- .../workbench/contrib/terminal/browser/xterm/decorationAddon.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index d3284f43a86..e7e3137ae30 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -354,11 +354,16 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { private async _getCommandActions(command: ITerminalCommand): Promise { const actions: IAction[] = []; if (command.command !== '') { - const label = localize("terminal.rerunCommand", 'Rerun Command'); + const labelRun = localize("terminal.rerunCommand", 'Rerun Command'); actions.push({ - class: undefined, tooltip: label, dispose: () => { }, id: 'terminal.rerunCommand', label, enabled: true, + class: undefined, tooltip: labelRun, dispose: () => { }, id: 'terminal.rerunCommand', label: labelRun, enabled: true, run: () => this._onDidRequestRunCommand.fire({ command }) }); + const labelCopy = localize("terminal.copyCommand", 'Copy Command'); + actions.push({ + class: undefined, tooltip: labelCopy, dispose: () => { }, id: 'terminal.copyCommand', label: labelCopy, enabled: true, + run: () => this._clipboardService.writeText(command.command) + }); } if (command.hasOutput) { if (actions.length > 0) { -- cgit v1.2.3 From 877f2c3bd0fbc29c701813f57e529e947a1b4d28 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 5 Jul 2022 11:32:21 -0700 Subject: testing: don't make testing a workspace view For #153513 --- src/vs/workbench/contrib/testing/browser/testing.contribution.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts index 3605c75ec80..f6580294960 100644 --- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -87,7 +87,6 @@ viewsRegistry.registerViews([{ name: localize('testExplorer', "Test Explorer"), ctorDescriptor: new SyncDescriptor(TestingExplorerView), canToggleVisibility: true, - workspace: true, canMoveView: true, weight: 80, order: -999, -- cgit v1.2.3 From 87635892d81b2eb255fc412927f0ec48e6aef421 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Tue, 5 Jul 2022 12:08:42 -0700 Subject: debug/testing: fixup localized commands For #153865 --- .../contrib/debug/browser/callStackView.ts | 4 +- .../contrib/debug/browser/debug.contribution.ts | 29 +++++++------- .../contrib/debug/browser/debugCommands.ts | 37 ++++++++--------- .../contrib/debug/browser/debugToolBar.ts | 4 +- .../contrib/testing/browser/testExplorerActions.ts | 46 +++++++++++----------- .../testing/browser/testingExplorerFilter.ts | 2 +- .../contrib/testing/browser/testingOutputPeek.ts | 8 ++-- 7 files changed, 66 insertions(+), 64 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 2eb3d65a818..b97d040674f 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -21,7 +21,7 @@ import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle' import { posix } from 'vs/base/common/path'; import { commonSuffixLength } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; -import { Icon } from 'vs/platform/action/common/action'; +import { ICommandActionTitle, Icon } from 'vs/platform/action/common/action'; import { createAndFillInActionBarActions, createAndFillInContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenuService, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -1120,7 +1120,7 @@ registerAction2(class Collapse extends ViewAction { } }); -function registerCallStackInlineMenuItem(id: string, title: string, icon: Icon, when: ContextKeyExpression, order: number, precondition?: ContextKeyExpression): void { +function registerCallStackInlineMenuItem(id: string, title: string | ICommandActionTitle, icon: Icon, when: ContextKeyExpression, order: number, precondition?: ContextKeyExpression): void { MenuRegistry.appendMenuItem(MenuId.DebugCallStackContext, { group: 'inline', order, diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 11584519c47..bebcecd9455 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -20,7 +20,7 @@ import { } from 'vs/workbench/contrib/debug/common/debug'; import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import { DebugService } from 'vs/workbench/contrib/debug/browser/debugService'; -import { ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SET_EXPRESSION_COMMAND_ID, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, SHOW_LOADED_SCRIPTS_ID, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, STEP_INTO_TARGET_LABEL, STEP_INTO_TARGET_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; +import { ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SET_EXPRESSION_COMMAND_ID, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, SHOW_LOADED_SCRIPTS_ID, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, STEP_INTO_TARGET_LABEL, STEP_INTO_TARGET_ID, DEBUG_COMMAND_CATEGORY } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; @@ -55,7 +55,7 @@ import { DisassemblyView, DisassemblyViewContribution } from 'vs/workbench/contr import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput'; import { DebugLifecycle } from 'vs/workbench/contrib/debug/common/debugLifecycle'; -import { Icon } from 'vs/platform/action/common/action'; +import { ICommandActionTitle, Icon } from 'vs/platform/action/common/action'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { DebugConsoleQuickAccess } from 'vs/workbench/contrib/debug/browser/debugConsoleQuickAccess'; @@ -98,20 +98,21 @@ registerEditorContribution('editor.contrib.callStack', CallStackEditorContributi registerEditorContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointEditorContribution); registerEditorContribution(EDITOR_CONTRIBUTION_ID, DebugEditorContribution); -const registerDebugCommandPaletteItem = (id: string, title: string, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => { +const registerDebugCommandPaletteItem = (id: string, title: ICommandActionTitle, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, when), group: debugCategory, command: { id, - title: `Debug: ${title}`, + title, + category: DEBUG_COMMAND_CATEGORY, precondition } }); }; registerDebugCommandPaletteItem(RESTART_SESSION_ID, RESTART_LABEL); -registerDebugCommandPaletteItem(TERMINATE_THREAD_ID, nls.localize('terminateThread', "Terminate Thread"), CONTEXT_IN_DEBUG_MODE); +registerDebugCommandPaletteItem(TERMINATE_THREAD_ID, { value: nls.localize('terminateThread', "Terminate Thread"), original: 'Terminate Thread' }, CONTEXT_IN_DEBUG_MODE); registerDebugCommandPaletteItem(STEP_OVER_ID, STEP_OVER_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugCommandPaletteItem(STEP_INTO_ID, STEP_INTO_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); registerDebugCommandPaletteItem(STEP_INTO_TARGET_ID, STEP_INTO_TARGET_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.and(CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'))); @@ -121,13 +122,13 @@ registerDebugCommandPaletteItem(DISCONNECT_ID, DISCONNECT_LABEL, CONTEXT_IN_DEBU registerDebugCommandPaletteItem(DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.or(CONTEXT_FOCUSED_SESSION_IS_ATTACH, ContextKeyExpr.and(CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED))); registerDebugCommandPaletteItem(STOP_ID, STOP_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.or(CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED)); registerDebugCommandPaletteItem(CONTINUE_ID, CONTINUE_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); -registerDebugCommandPaletteItem(FOCUS_REPL_ID, nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, 'Focus on Debug Console View')); -registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, nls.localize('jumpToCursor', "Jump to Cursor"), CONTEXT_JUMP_TO_CURSOR_SUPPORTED); -registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, nls.localize('SetNextStatement', "Set Next Statement"), CONTEXT_JUMP_TO_CURSOR_SUPPORTED); -registerDebugCommandPaletteItem(RunToCursorAction.ID, RunToCursorAction.LABEL, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'))); -registerDebugCommandPaletteItem(SelectionToReplAction.ID, SelectionToReplAction.LABEL, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE)); -registerDebugCommandPaletteItem(SelectionToWatchExpressionsAction.ID, SelectionToWatchExpressionsAction.LABEL, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE)); -registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, nls.localize('inlineBreakpoint', "Inline Breakpoint")); +registerDebugCommandPaletteItem(FOCUS_REPL_ID, { value: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, 'Focus on Debug Console View'), original: 'Focus on Debug Console View' }); +registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, { value: nls.localize('jumpToCursor', "Jump to Cursor"), original: 'Jump to Cursor' }, CONTEXT_JUMP_TO_CURSOR_SUPPORTED); +registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, { value: nls.localize('SetNextStatement', "Set Next Statement"), original: 'Set Next Statement' }, CONTEXT_JUMP_TO_CURSOR_SUPPORTED); +registerDebugCommandPaletteItem(RunToCursorAction.ID, { value: RunToCursorAction.LABEL, original: 'Run to Cursor' }, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'))); +registerDebugCommandPaletteItem(SelectionToReplAction.ID, { value: SelectionToReplAction.LABEL, original: 'Evaluate in Debug Console' }, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE)); +registerDebugCommandPaletteItem(SelectionToWatchExpressionsAction.ID, { value: SelectionToWatchExpressionsAction.LABEL, original: 'Add to Watch' }, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE)); +registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, { value: nls.localize('inlineBreakpoint', "Inline Breakpoint"), original: 'Inline Breakpoint' }); registerDebugCommandPaletteItem(DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing)))); registerDebugCommandPaletteItem(DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing)))); registerDebugCommandPaletteItem(SELECT_AND_START_ID, SELECT_AND_START_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing)))); @@ -138,7 +139,7 @@ registerDebugCommandPaletteItem(SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LA // Debug callstack context menu -const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation', icon?: Icon) => { +const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string | ICommandActionTitle, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation', icon?: Icon) => { MenuRegistry.appendMenuItem(menuId, { group, when, @@ -186,7 +187,7 @@ registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_WATCH_EXPRESSIONS_COM // Touch Bar if (isMacintosh) { - const registerTouchBarEntry = (id: string, title: string, order: number, when: ContextKeyExpression | undefined, iconUri: URI) => { + const registerTouchBarEntry = (id: string, title: string | ICommandActionTitle, order: number, when: ContextKeyExpression | undefined, iconUri: URI) => { MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { command: { id, diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 49eb8197d7a..86fa5b8c01b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -65,26 +65,27 @@ export const NEXT_DEBUG_CONSOLE_ID = 'workbench.action.debug.nextConsole'; export const PREV_DEBUG_CONSOLE_ID = 'workbench.action.debug.prevConsole'; export const SHOW_LOADED_SCRIPTS_ID = 'workbench.action.debug.showLoadedScripts'; -export const RESTART_LABEL = nls.localize('restartDebug', "Restart"); -export const STEP_OVER_LABEL = nls.localize('stepOverDebug', "Step Over"); -export const STEP_INTO_LABEL = nls.localize('stepIntoDebug', "Step Into"); -export const STEP_INTO_TARGET_LABEL = nls.localize('stepIntoTargetDebug', "Step Into Target"); -export const STEP_OUT_LABEL = nls.localize('stepOutDebug', "Step Out"); -export const PAUSE_LABEL = nls.localize('pauseDebug', "Pause"); -export const DISCONNECT_LABEL = nls.localize('disconnect', "Disconnect"); -export const DISCONNECT_AND_SUSPEND_LABEL = nls.localize('disconnectSuspend', "Disconnect and Suspend"); -export const STOP_LABEL = nls.localize('stop', "Stop"); -export const CONTINUE_LABEL = nls.localize('continueDebug', "Continue"); -export const FOCUS_SESSION_LABEL = nls.localize('focusSession', "Focus Session"); -export const SELECT_AND_START_LABEL = nls.localize('selectAndStartDebugging', "Select and Start Debugging"); +export const DEBUG_COMMAND_CATEGORY = 'Debug'; +export const RESTART_LABEL = { value: nls.localize('restartDebug', "Restart"), original: 'Restart' }; +export const STEP_OVER_LABEL = { value: nls.localize('stepOverDebug', "Step Over"), original: 'Step Over' }; +export const STEP_INTO_LABEL = { value: nls.localize('stepIntoDebug', "Step Into"), original: 'Step Into' }; +export const STEP_INTO_TARGET_LABEL = { value: nls.localize('stepIntoTargetDebug', "Step Into Target"), original: 'Step Into Target' }; +export const STEP_OUT_LABEL = { value: nls.localize('stepOutDebug', "Step Out"), original: 'Step Out' }; +export const PAUSE_LABEL = { value: nls.localize('pauseDebug', "Pause"), original: 'Pause' }; +export const DISCONNECT_LABEL = { value: nls.localize('disconnect', "Disconnect"), original: 'Disconnect' }; +export const DISCONNECT_AND_SUSPEND_LABEL = { value: nls.localize('disconnectSuspend', "Disconnect and Suspend"), original: 'Disconnect and Suspend' }; +export const STOP_LABEL = { value: nls.localize('stop', "Stop"), original: 'Stop' }; +export const CONTINUE_LABEL = { value: nls.localize('continueDebug', "Continue"), original: 'Continue' }; +export const FOCUS_SESSION_LABEL = { value: nls.localize('focusSession', "Focus Session"), original: 'Focus Session' }; +export const SELECT_AND_START_LABEL = { value: nls.localize('selectAndStartDebugging', "Select and Start Debugging"), original: 'Select and Start Debugging' }; export const DEBUG_CONFIGURE_LABEL = nls.localize('openLaunchJson', "Open '{0}'", 'launch.json'); -export const DEBUG_START_LABEL = nls.localize('startDebug', "Start Debugging"); -export const DEBUG_RUN_LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging"); -export const NEXT_DEBUG_CONSOLE_LABEL = nls.localize('nextDebugConsole', "Focus Next Debug Console"); -export const PREV_DEBUG_CONSOLE_LABEL = nls.localize('prevDebugConsole', "Focus Previous Debug Console"); -export const OPEN_LOADED_SCRIPTS_LABEL = nls.localize('openLoadedScript', "Open Loaded Script..."); +export const DEBUG_START_LABEL = { value: nls.localize('startDebug', "Start Debugging"), original: 'Start Debugging' }; +export const DEBUG_RUN_LABEL = { value: nls.localize('startWithoutDebugging', "Start Without Debugging"), original: 'Start Without Debugging' }; +export const NEXT_DEBUG_CONSOLE_LABEL = { value: nls.localize('nextDebugConsole', "Focus Next Debug Console"), original: 'Focus Next Debug Console' }; +export const PREV_DEBUG_CONSOLE_LABEL = { value: nls.localize('prevDebugConsole', "Focus Previous Debug Console"), original: 'Focus Previous Debug Console' }; +export const OPEN_LOADED_SCRIPTS_LABEL = { value: nls.localize('openLoadedScript', "Open Loaded Script..."), original: 'Open Loaded Script...' }; -export const SELECT_DEBUG_CONSOLE_LABEL = nls.localize('selectDebugConsole', "Select Debug Console"); +export const SELECT_DEBUG_CONSOLE_LABEL = { value: nls.localize('selectDebugConsole', "Select Debug Console"), original: 'Select Debug Console' }; export const DEBUG_QUICK_ACCESS_PREFIX = 'debug '; export const DEBUG_CONSOLE_QUICK_ACCESS_PREFIX = 'debug consoles '; diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 1f7614bfee7..3fd1c0238b8 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -16,7 +16,7 @@ import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/debugToolBar'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; -import { ICommandAction } from 'vs/platform/action/common/action'; +import { ICommandAction, ICommandActionTitle } from 'vs/platform/action/common/action'; import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem'; import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IMenu, IMenuService, MenuId, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; @@ -296,7 +296,7 @@ export function createDisconnectMenuItemAction(action: MenuItemAction, disposabl // Debug toolbar const debugViewTitleItems: IDisposable[] = []; -const registerDebugToolBarItem = (id: string, title: string, order: number, icon?: { light?: URI; dark?: URI } | ThemeIcon, when?: ContextKeyExpression, precondition?: ContextKeyExpression, alt?: ICommandAction) => { +const registerDebugToolBarItem = (id: string, title: string | ICommandActionTitle, order: number, icon?: { light?: URI; dark?: URI } | ThemeIcon, when?: ContextKeyExpression, precondition?: ContextKeyExpression, alt?: ICommandAction) => { MenuRegistry.appendMenuItem(MenuId.DebugToolBar, { group: 'navigation', when, diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index db687ec08b6..556f7f4e65d 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -406,7 +406,7 @@ export class CancelTestRunAction extends Action2 { constructor() { super({ id: TestCommandId.CancelTestRunAction, - title: localize('testing.cancelRun', "Cancel Test Run"), + title: { value: localize('testing.cancelRun', "Cancel Test Run"), original: 'Cancel Test Run' }, icon: icons.testingCancelIcon, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -443,7 +443,7 @@ export class TestingViewAsListAction extends ViewAction { super({ id: TestCommandId.TestingViewAsListAction, viewId: Testing.ExplorerViewId, - title: localize('testing.viewAsList', "View as List"), + title: { value: localize('testing.viewAsList', "View as List"), original: 'View as List' }, toggled: TestingContextKeys.viewMode.isEqualTo(TestExplorerViewMode.List), menu: { id: MenuId.ViewTitle, @@ -467,7 +467,7 @@ export class TestingViewAsTreeAction extends ViewAction { super({ id: TestCommandId.TestingViewAsTreeAction, viewId: Testing.ExplorerViewId, - title: localize('testing.viewAsTree', "View as Tree"), + title: { value: localize('testing.viewAsTree', "View as Tree"), original: 'View as Tree' }, toggled: TestingContextKeys.viewMode.isEqualTo(TestExplorerViewMode.Tree), menu: { id: MenuId.ViewTitle, @@ -492,7 +492,7 @@ export class TestingSortByStatusAction extends ViewAction { super({ id: TestCommandId.TestingSortByStatusAction, viewId: Testing.ExplorerViewId, - title: localize('testing.sortByStatus', "Sort by Status"), + title: { value: localize('testing.sortByStatus', "Sort by Status"), original: 'Sort by Status' }, toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByStatus), menu: { id: MenuId.ViewTitle, @@ -516,7 +516,7 @@ export class TestingSortByLocationAction extends ViewAction super({ id: TestCommandId.TestingSortByLocationAction, viewId: Testing.ExplorerViewId, - title: localize('testing.sortByLocation', "Sort by Location"), + title: { value: localize('testing.sortByLocation', "Sort by Location"), original: 'Sort by Location' }, toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByLocation), menu: { id: MenuId.ViewTitle, @@ -540,7 +540,7 @@ export class TestingSortByDurationAction extends ViewAction super({ id: TestCommandId.TestingSortByDurationAction, viewId: Testing.ExplorerViewId, - title: localize('testing.sortByDuration', "Sort by Duration"), + title: { value: localize('testing.sortByDuration', "Sort by Duration"), original: 'Sort by Duration' }, toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByDuration), menu: { id: MenuId.ViewTitle, @@ -563,7 +563,7 @@ export class ShowMostRecentOutputAction extends Action2 { constructor() { super({ id: TestCommandId.ShowMostRecentOutputAction, - title: localize('testing.showMostRecentOutput', "Show Output"), + title: { value: localize('testing.showMostRecentOutput', "Show Output"), original: 'Show Output' }, category, icon: Codicon.terminal, keybinding: { @@ -594,7 +594,7 @@ export class CollapseAllAction extends ViewAction { super({ id: TestCommandId.CollapseAllAction, viewId: Testing.ExplorerViewId, - title: localize('testing.collapseAll', "Collapse All Tests"), + title: { value: localize('testing.collapseAll', "Collapse All Tests"), original: 'Collapse All Tests' }, icon: Codicon.collapseAll, menu: { id: MenuId.ViewTitle, @@ -617,7 +617,7 @@ export class ClearTestResultsAction extends Action2 { constructor() { super({ id: TestCommandId.ClearTestResultsAction, - title: localize('testing.clearResults', "Clear All Results"), + title: { value: localize('testing.clearResults', "Clear All Results"), original: 'Clear All Results' }, category, icon: Codicon.trash, menu: [{ @@ -646,7 +646,7 @@ export class GoToTest extends Action2 { constructor() { super({ id: TestCommandId.GoToTest, - title: localize('testing.editFocusedTest', "Go to Test"), + title: { value: localize('testing.editFocusedTest', "Go to Test"), original: 'Go to Test' }, icon: Codicon.goToFile, menu: testItemInlineAndInContext(ActionOrder.GoToTest, TestingContextKeys.testItemHasUri.isEqualTo(true)), keybinding: { @@ -742,7 +742,7 @@ export class RunAtCursor extends ExecuteTestAtCursor { constructor() { super({ id: TestCommandId.RunAtCursor, - title: localize('testing.runAtCursor', "Run Test at Cursor"), + title: { value: localize('testing.runAtCursor', "Run Test at Cursor"), original: 'Run Test at Cursor' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -757,7 +757,7 @@ export class DebugAtCursor extends ExecuteTestAtCursor { constructor() { super({ id: TestCommandId.DebugAtCursor, - title: localize('testing.debugAtCursor', "Debug Test at Cursor"), + title: { value: localize('testing.debugAtCursor', "Debug Test at Cursor"), original: 'Debug Test at Cursor' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -824,7 +824,7 @@ export class RunCurrentFile extends ExecuteTestsInCurrentFile { constructor() { super({ id: TestCommandId.RunCurrentFile, - title: localize('testing.runCurrentFile', "Run Tests in Current File"), + title: { value: localize('testing.runCurrentFile', "Run Tests in Current File"), original: 'Run Tests in Current File' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -840,7 +840,7 @@ export class DebugCurrentFile extends ExecuteTestsInCurrentFile { constructor() { super({ id: TestCommandId.DebugCurrentFile, - title: localize('testing.debugCurrentFile', "Debug Tests in Current File"), + title: { value: localize('testing.debugCurrentFile', "Debug Tests in Current File"), original: 'Debug Tests in Current File' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -948,7 +948,7 @@ export class ReRunFailedTests extends RunOrDebugFailedTests { constructor() { super({ id: TestCommandId.ReRunFailedTests, - title: localize('testing.reRunFailTests', "Rerun Failed Tests"), + title: { value: localize('testing.reRunFailTests', "Rerun Failed Tests"), original: 'Rerun Failed Tests' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -969,7 +969,7 @@ export class DebugFailedTests extends RunOrDebugFailedTests { constructor() { super({ id: TestCommandId.DebugFailedTests, - title: localize('testing.debugFailTests', "Debug Failed Tests"), + title: { value: localize('testing.debugFailTests', "Debug Failed Tests"), original: 'Debug Failed Tests' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -990,7 +990,7 @@ export class ReRunLastRun extends RunOrDebugLastRun { constructor() { super({ id: TestCommandId.ReRunLastRun, - title: localize('testing.reRunLastRun', "Rerun Last Run"), + title: { value: localize('testing.reRunLastRun', "Rerun Last Run"), original: 'Rerun Last Run' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1011,7 +1011,7 @@ export class DebugLastRun extends RunOrDebugLastRun { constructor() { super({ id: TestCommandId.DebugLastRun, - title: localize('testing.debugLastRun', "Debug Last Run"), + title: { value: localize('testing.debugLastRun', "Debug Last Run"), original: 'Debug Last Run' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1032,7 +1032,7 @@ export class SearchForTestExtension extends Action2 { constructor() { super({ id: TestCommandId.SearchForTestExtension, - title: localize('testing.searchForTestExtension', "Search for Test Extension"), + title: { value: localize('testing.searchForTestExtension', "Search for Test Extension"), original: 'Search for Test Extension' }, }); } @@ -1048,7 +1048,7 @@ export class OpenOutputPeek extends Action2 { constructor() { super({ id: TestCommandId.OpenOutputPeek, - title: localize('testing.openOutputPeek', "Peek Output"), + title: { value: localize('testing.openOutputPeek', "Peek Output"), original: 'Peek Output' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1070,7 +1070,7 @@ export class ToggleInlineTestOutput extends Action2 { constructor() { super({ id: TestCommandId.ToggleInlineTestOutput, - title: localize('testing.toggleInlineTestOutput', "Toggle Inline Test Output"), + title: { value: localize('testing.toggleInlineTestOutput', "Toggle Inline Test Output"), original: 'Toggle Inline Test Output' }, category, keybinding: { weight: KeybindingWeight.WorkbenchContrib, @@ -1119,7 +1119,7 @@ export class RefreshTestsAction extends Action2 { constructor() { super({ id: TestCommandId.RefreshTestsAction, - title: localize('testing.refreshTests', "Refresh Tests"), + title: { value: localize('testing.refreshTests', "Refresh Tests"), original: 'Refresh Tests' }, category, icon: icons.testingRefreshTests, keybinding: { @@ -1155,7 +1155,7 @@ export class CancelTestRefreshAction extends Action2 { constructor() { super({ id: TestCommandId.CancelTestRefreshAction, - title: localize('testing.cancelTestRefresh', "Cancel Test Refresh"), + title: { value: localize('testing.cancelTestRefresh', "Cancel Test Refresh"), original: 'Cancel Test Refresh' }, category, icon: icons.testingCancelRefreshTests, menu: refreshMenus(true), diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts index 4d1af363aa7..52d57770b21 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts @@ -252,7 +252,7 @@ registerAction2(class extends Action2 { constructor() { super({ id: TestCommandId.FilterAction, - title: localize('filter', "Filter"), + title: { value: localize('filter', "Filter"), original: 'Filter' }, }); } async run(): Promise { } diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index c3452de68dd..988ad294015 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -1666,7 +1666,7 @@ export class GoToNextMessageAction extends EditorAction2 { super({ id: GoToNextMessageAction.ID, f1: true, - title: localize('testing.goToNextMessage', "Go to Next Test Failure"), + title: { value: localize('testing.goToNextMessage', "Go to Next Test Failure"), original: 'Go to Next Test Failure' }, icon: Codicon.arrowDown, category: CATEGORIES.Test, keybinding: { @@ -1696,7 +1696,7 @@ export class GoToPreviousMessageAction extends EditorAction2 { super({ id: GoToPreviousMessageAction.ID, f1: true, - title: localize('testing.goToPreviousMessage', "Go to Previous Test Failure"), + title: { value: localize('testing.goToPreviousMessage', "Go to Previous Test Failure"), original: 'Go to Previous Test Failure' }, icon: Codicon.arrowUp, category: CATEGORIES.Test, keybinding: { @@ -1726,7 +1726,7 @@ export class OpenMessageInEditorAction extends EditorAction2 { super({ id: OpenMessageInEditorAction.ID, f1: false, - title: localize('testing.openMessageInEditor', "Open in Editor"), + title: { value: localize('testing.openMessageInEditor', "Open in Editor"), original: 'Open in Editor' }, icon: Codicon.linkExternal, category: CATEGORIES.Test, menu: [{ id: MenuId.TestPeekTitle }], @@ -1744,7 +1744,7 @@ export class ToggleTestingPeekHistory extends EditorAction2 { super({ id: ToggleTestingPeekHistory.ID, f1: true, - title: localize('testing.toggleTestingPeekHistory', "Toggle Test History in Peek"), + title: { value: localize('testing.toggleTestingPeekHistory', "Toggle Test History in Peek"), original: 'Toggle Test History in Peek' }, icon: Codicon.history, category: CATEGORIES.Test, menu: [{ -- cgit v1.2.3 From 934408aea7883601032febd844796f826ca8ecd2 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 6 Jul 2022 11:03:38 -0700 Subject: Debt - `sessionSync` -> `editSessions` (#154289) --- .../browser/editSessions.contribution.ts | 517 +++++++++++++++++++++ .../browser/editSessionsWorkbenchService.ts | 358 ++++++++++++++ .../contrib/editSessions/common/editSessions.ts | 67 +++ .../editSessions/common/editSessionsLogService.ts | 50 ++ .../editSessions/test/browser/editSessions.test.ts | 134 ++++++ .../browser/sessionSync.contribution.ts | 517 --------------------- .../browser/sessionSyncWorkbenchService.ts | 358 -------------- .../sessionSync/common/editSessionsLogService.ts | 50 -- .../contrib/sessionSync/common/sessionSync.ts | 67 --- .../sessionSync/test/browser/sessionSync.test.ts | 134 ------ 10 files changed, 1126 insertions(+), 1126 deletions(-) create mode 100644 src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts create mode 100644 src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts create mode 100644 src/vs/workbench/contrib/editSessions/common/editSessions.ts create mode 100644 src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts create mode 100644 src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts delete mode 100644 src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts delete mode 100644 src/vs/workbench/contrib/sessionSync/browser/sessionSyncWorkbenchService.ts delete mode 100644 src/vs/workbench/contrib/sessionSync/common/editSessionsLogService.ts delete mode 100644 src/vs/workbench/contrib/sessionSync/common/sessionSync.ts delete mode 100644 src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts new file mode 100644 index 00000000000..deb0cb486bc --- /dev/null +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -0,0 +1,517 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { Action2, IAction2Options, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { localize } from 'vs/nls'; +import { IEditSessionsWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EditSessionSchemaVersion, IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions'; +import { ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { URI } from 'vs/base/common/uri'; +import { joinPath, relativePath } from 'vs/base/common/resources'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { EditSessionsWorkbenchService } from 'vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { UserDataSyncErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtualWorkspace'; +import { Schemas } from 'vs/base/common/network'; +import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; +import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { EditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessionsLogService'; + +registerSingleton(IEditSessionsLogService, EditSessionsLogService); +registerSingleton(IEditSessionsWorkbenchService, EditSessionsWorkbenchService); + +const continueEditSessionCommand: IAction2Options = { + id: '_workbench.experimental.editSessions.actions.continueEditSession', + title: { value: localize('continue edit session', "Continue Edit Session..."), original: 'Continue Edit Session...' }, + category: EDIT_SESSION_SYNC_CATEGORY, + f1: true +}; +const openLocalFolderCommand: IAction2Options = { + id: '_workbench.experimental.editSessions.actions.continueEditSession.openLocalFolder', + title: { value: localize('continue edit session in local folder', "Open In Local Folder"), original: 'Open In Local Folder' }, + category: EDIT_SESSION_SYNC_CATEGORY, + precondition: IsWebContext +}; +const queryParamName = 'editSessionId'; +const experimentalSettingName = 'workbench.experimental.editSessions.enabled'; + +export class EditSessionsContribution extends Disposable implements IWorkbenchContribution { + + private registered = false; + private continueEditSessionOptions: ContinueEditSessionItem[] = []; + + constructor( + @IEditSessionsWorkbenchService private readonly editSessionsWorkbenchService: IEditSessionsWorkbenchService, + @IFileService private readonly fileService: IFileService, + @IProgressService private readonly progressService: IProgressService, + @IOpenerService private readonly openerService: IOpenerService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @ISCMService private readonly scmService: ISCMService, + @INotificationService private readonly notificationService: INotificationService, + @IDialogService private readonly dialogService: IDialogService, + @IEditSessionsLogService private readonly logService: IEditSessionsLogService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IProductService private readonly productService: IProductService, + @IConfigurationService private configurationService: IConfigurationService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @ICommandService private commandService: ICommandService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IFileDialogService private readonly fileDialogService: IFileDialogService + ) { + super(); + + if (this.environmentService.editSessionId !== undefined) { + void this.applyEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined); + } + + this.configurationService.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration(experimentalSettingName)) { + this.registerActions(); + } + }); + + this.registerActions(); + + continueEditSessionExtPoint.setHandler(extensions => { + const continueEditSessionOptions: ContinueEditSessionItem[] = []; + for (const extension of extensions) { + if (!isProposedApiEnabled(extension.description, 'contribEditSessions')) { + continue; + } + if (!Array.isArray(extension.value)) { + continue; + } + const commands = new Map((extension.description.contributes?.commands ?? []).map(c => [c.command, c])); + for (const contribution of extension.value) { + if (!contribution.command || !contribution.group || !contribution.when) { + continue; + } + const fullCommand = commands.get(contribution.command); + if (!fullCommand) { return; } + + continueEditSessionOptions.push(new ContinueEditSessionItem( + fullCommand.title, + fullCommand.command, + ContextKeyExpr.deserialize(contribution.when) + )); + } + } + this.continueEditSessionOptions = continueEditSessionOptions; + }); + } + + private registerActions() { + if (this.registered || this.configurationService.getValue(experimentalSettingName) !== true) { + this.logService.info(`Skipping registering edit sessions actions as edit sessions are currently disabled. Set ${experimentalSettingName} to enable edit sessions.`); + return; + } + + this.registerContinueEditSessionAction(); + + this.registerApplyLatestEditSessionAction(); + this.registerStoreLatestEditSessionAction(); + + this.registerContinueInLocalFolderAction(); + + this.registered = true; + } + + private registerContinueEditSessionAction() { + const that = this; + this._register(registerAction2(class ContinueEditSessionAction extends Action2 { + constructor() { + super(continueEditSessionCommand); + } + + async run(accessor: ServicesAccessor, workspaceUri: URI | undefined): Promise { + let uri = workspaceUri ?? await that.pickContinueEditSessionDestination(); + if (uri === undefined) { return; } + + // Run the store action to get back a ref + const ref = await that.storeEditSession(false); + + // Append the ref to the URI + if (ref !== undefined) { + const encodedRef = encodeURIComponent(ref); + uri = uri.with({ + query: uri.query.length > 0 ? (uri + `&${queryParamName}=${encodedRef}`) : `${queryParamName}=${encodedRef}` + }); + } else { + that.logService.warn(`Failed to store edit session when invoking ${continueEditSessionCommand.id}.`); + } + + // Open the URI + that.logService.info(`Opening ${uri.toString()}`); + await that.openerService.open(uri, { openExternal: true }); + } + })); + } + + private registerApplyLatestEditSessionAction(): void { + const that = this; + this._register(registerAction2(class ApplyLatestEditSessionAction extends Action2 { + constructor() { + super({ + id: 'workbench.experimental.editSessions.actions.resumeLatest', + title: { value: localize('resume latest.v2', "Resume Latest Edit Session"), original: 'Resume Latest Edit Session' }, + category: EDIT_SESSION_SYNC_CATEGORY, + f1: true, + }); + } + + async run(accessor: ServicesAccessor): Promise { + await that.progressService.withProgress({ + location: ProgressLocation.Notification, + title: localize('applying edit session', 'Applying edit session...') + }, async () => await that.applyEditSession()); + } + })); + } + + private registerStoreLatestEditSessionAction(): void { + const that = this; + this._register(registerAction2(class StoreLatestEditSessionAction extends Action2 { + constructor() { + super({ + id: 'workbench.experimental.editSessions.actions.storeCurrent', + title: { value: localize('store current.v2', "Store Current Edit Session"), original: 'Store Current Edit Session' }, + category: EDIT_SESSION_SYNC_CATEGORY, + f1: true, + }); + } + + async run(accessor: ServicesAccessor): Promise { + await that.progressService.withProgress({ + location: ProgressLocation.Notification, + title: localize('storing edit session', 'Storing edit session...') + }, async () => await that.storeEditSession(true)); + } + })); + } + + async applyEditSession(ref?: string): Promise { + if (ref !== undefined) { + this.logService.info(`Applying edit session with ref ${ref}.`); + } + + const data = await this.editSessionsWorkbenchService.read(ref); + if (!data) { + if (ref === undefined) { + this.notificationService.info(localize('no edit session', 'There are no edit sessions to apply.')); + } else { + this.notificationService.warn(localize('no edit session content for ref', 'Could not apply edit session contents for ID {0}.', ref)); + } + this.logService.info(`Aborting applying edit session as no edit session content is available to be applied from ref ${ref}.`); + return; + } + const editSession = data.editSession; + ref = data.ref; + + if (editSession.version > EditSessionSchemaVersion) { + this.notificationService.error(localize('client too old', "Please upgrade to a newer version of {0} to apply this edit session.", this.productService.nameLong)); + return; + } + + try { + const changes: ({ uri: URI; type: ChangeType; contents: string | undefined })[] = []; + let hasLocalUncommittedChanges = false; + + for (const folder of editSession.folders) { + const folderRoot = this.contextService.getWorkspace().folders.find((f) => f.name === folder.name); + if (!folderRoot) { + this.logService.info(`Skipping applying ${folder.workingChanges.length} changes from edit session with ref ${ref} as no corresponding workspace folder named ${folder.name} is currently open.`); + continue; + } + + for (const repository of this.scmService.repositories) { + if (repository.provider.rootUri !== undefined && + this.contextService.getWorkspaceFolder(repository.provider.rootUri)?.name === folder.name && + this.getChangedResources(repository).length > 0 + ) { + hasLocalUncommittedChanges = true; + break; + } + } + + for (const { relativeFilePath, contents, type } of folder.workingChanges) { + const uri = joinPath(folderRoot.uri, relativeFilePath); + changes.push({ uri: uri, type: type, contents: contents }); + } + } + + if (hasLocalUncommittedChanges) { + // TODO@joyceerhl Provide the option to diff files which would be overwritten by edit session contents + const result = await this.dialogService.confirm({ + message: localize('apply edit session warning', 'Applying your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'), + type: 'warning', + title: EDIT_SESSION_SYNC_CATEGORY.value + }); + if (!result.confirmed) { + return; + } + } + + for (const { uri, type, contents } of changes) { + if (type === ChangeType.Addition) { + await this.fileService.writeFile(uri, VSBuffer.fromString(contents!)); + } else if (type === ChangeType.Deletion && await this.fileService.exists(uri)) { + await this.fileService.del(uri); + } + } + + this.logService.info(`Deleting edit session with ref ${ref} after successfully applying it to current workspace...`); + await this.editSessionsWorkbenchService.delete(ref); + this.logService.info(`Deleted edit session with ref ${ref}.`); + } catch (ex) { + this.logService.error('Failed to apply edit session, reason: ', (ex as Error).toString()); + this.notificationService.error(localize('apply failed', "Failed to apply your edit session.")); + } + } + + async storeEditSession(fromStoreCommand: boolean): Promise { + const folders: Folder[] = []; + let hasEdits = false; + + for (const repository of this.scmService.repositories) { + // Look through all resource groups and compute which files were added/modified/deleted + const trackedUris = this.getChangedResources(repository); // A URI might appear in more than one resource group + + const workingChanges: Change[] = []; + let name = repository.provider.rootUri ? this.contextService.getWorkspaceFolder(repository.provider.rootUri)?.name : undefined; + + for (const uri of trackedUris) { + const workspaceFolder = this.contextService.getWorkspaceFolder(uri); + if (!workspaceFolder) { + this.logService.info(`Skipping working change ${uri.toString()} as no associated workspace folder was found.`); + + continue; + } + + name = name ?? workspaceFolder.name; + const relativeFilePath = relativePath(workspaceFolder.uri, uri) ?? uri.path; + + // Only deal with file contents for now + try { + if (!(await this.fileService.stat(uri)).isFile) { + continue; + } + } catch { } + + hasEdits = true; + + if (await this.fileService.exists(uri)) { + workingChanges.push({ type: ChangeType.Addition, fileType: FileType.File, contents: (await this.fileService.readFile(uri)).value.toString(), relativeFilePath: relativeFilePath }); + } else { + // Assume it's a deletion + workingChanges.push({ type: ChangeType.Deletion, fileType: FileType.File, contents: undefined, relativeFilePath: relativeFilePath }); + } + } + + folders.push({ workingChanges, name: name ?? '' }); + } + + if (!hasEdits) { + this.logService.info('Skipping storing edit session as there are no edits to store.'); + if (fromStoreCommand) { + this.notificationService.info(localize('no edits to store', 'Skipped storing edit session as there are no edits to store.')); + } + return undefined; + } + + const data: EditSession = { folders, version: 1 }; + + try { + this.logService.info(`Storing edit session...`); + const ref = await this.editSessionsWorkbenchService.write(data); + this.logService.info(`Stored edit session with ref ${ref}.`); + return ref; + } catch (ex) { + this.logService.error(`Failed to store edit session, reason: `, (ex as Error).toString()); + + type UploadFailedEvent = { reason: string }; + type UploadFailedClassification = { + owner: 'joyceerhl'; comment: 'Reporting when Continue On server request fails.'; + reason?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The reason that the server request failed.' }; + }; + + if (ex instanceof UserDataSyncStoreError) { + switch (ex.code) { + case UserDataSyncErrorCode.TooLarge: + // Uploading a payload can fail due to server size limits + this.telemetryService.publicLog2('editSessions.upload.failed', { reason: 'TooLarge' }); + this.notificationService.error(localize('payload too large', 'Your edit session exceeds the size limit and cannot be stored.')); + break; + default: + this.telemetryService.publicLog2('editSessions.upload.failed', { reason: 'unknown' }); + this.notificationService.error(localize('payload failed', 'Your edit session cannot be stored.')); + break; + } + } + } + + return undefined; + } + + private getChangedResources(repository: ISCMRepository) { + const trackedUris = repository.provider.groups.elements.reduce((resources, resourceGroups) => { + resourceGroups.elements.forEach((resource) => resources.add(resource.sourceUri)); + return resources; + }, new Set()); // A URI might appear in more than one resource group + + return [...trackedUris]; + } + + //#region Continue Edit Session extension contribution point + + private registerContinueInLocalFolderAction(): void { + const that = this; + this._register(registerAction2(class ContinueInLocalFolderAction extends Action2 { + constructor() { + super(openLocalFolderCommand); + } + + async run(accessor: ServicesAccessor): Promise { + const selection = await that.fileDialogService.showOpenDialog({ + title: localize('continueEditSession.openLocalFolder.title', 'Select a local folder to continue your edit session in'), + canSelectFolders: true, + canSelectMany: false, + canSelectFiles: false, + availableFileSystems: [Schemas.file] + }); + + return selection?.length !== 1 ? undefined : URI.from({ + scheme: that.productService.urlProtocol, + authority: Schemas.file, + path: selection[0].path + }); + } + })); + } + + private async pickContinueEditSessionDestination(): Promise { + const quickPick = this.quickInputService.createQuickPick(); + + quickPick.title = localize('continueEditSessionPick.title', 'Continue Edit Session...'); + quickPick.placeholder = localize('continueEditSessionPick.placeholder', 'Choose how you would like to continue working'); + quickPick.items = this.createPickItems(); + + const command = await new Promise((resolve, reject) => { + quickPick.onDidHide(() => resolve(undefined)); + + quickPick.onDidAccept((e) => { + const selection = quickPick.activeItems[0].command; + resolve(selection); + quickPick.hide(); + }); + + quickPick.show(); + }); + + quickPick.dispose(); + + if (command === undefined) { + return undefined; + } + + try { + const uri = await this.commandService.executeCommand(command); + return URI.isUri(uri) ? uri : undefined; + } catch (ex) { + return undefined; + } + } + + private createPickItems(): ContinueEditSessionItem[] { + const items = [...this.continueEditSessionOptions].filter((option) => option.when === undefined || this.contextKeyService.contextMatchesRules(option.when)); + + if (getVirtualWorkspaceLocation(this.contextService.getWorkspace()) !== undefined) { + items.push(new ContinueEditSessionItem( + localize('continueEditSessionItem.openInLocalFolder', 'Open In Local Folder'), + openLocalFolderCommand.id, + )); + } + + return items; + } +} + +class ContinueEditSessionItem implements IQuickPickItem { + constructor( + public readonly label: string, + public readonly command: string, + public readonly when?: ContextKeyExpression, + ) { } +} + +interface ICommand { + command: string; + group: string; + when: string; +} + +const continueEditSessionExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'continueEditSession', + jsonSchema: { + description: localize('continueEditSessionExtPoint', 'Contributes options for continuing the current edit session in a different environment'), + type: 'array', + items: { + type: 'object', + properties: { + command: { + description: localize('continueEditSessionExtPoint.command', 'Identifier of the command to execute. The command must be declared in the \'commands\'-section and return a URI representing a different environment where the current edit session can be continued.'), + type: 'string' + }, + group: { + description: localize('continueEditSessionExtPoint.group', 'Group into which this item belongs.'), + type: 'string' + }, + when: { + description: localize('continueEditSessionExtPoint.when', 'Condition which must be true to show this item.'), + type: 'string' + } + }, + required: ['command'] + } + } +}); + +//#endregion + +const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchRegistry.registerWorkbenchContribution(EditSessionsContribution, LifecyclePhase.Restored); + +Registry.as(Extensions.Configuration).registerConfiguration({ + ...workbenchConfigurationNodeBase, + 'properties': { + 'workbench.experimental.editSessions.enabled': { + 'type': 'boolean', + 'tags': ['experimental', 'usesOnlineServices'], + 'default': false, + 'markdownDescription': localize('editSessionsEnabled', "Controls whether to display cloud-enabled actions to store and resume uncommitted changes when switching between web, desktop, or devices."), + }, + } +}); diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts new file mode 100644 index 00000000000..af25b5ee15a --- /dev/null +++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts @@ -0,0 +1,358 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync'; +import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, IEditSessionsWorkbenchService, EDIT_SESSIONS_SIGNED_IN_KEY, IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions'; + +type ExistingSession = IQuickPickItem & { session: AuthenticationSession & { providerId: string } }; +type AuthenticationProviderOption = IQuickPickItem & { provider: IAuthenticationProvider }; + +export class EditSessionsWorkbenchService extends Disposable implements IEditSessionsWorkbenchService { + + _serviceBrand = undefined; + + private serverConfiguration = this.productService['editSessions.store']; + private storeClient: UserDataSyncStoreClient | undefined; + + #authenticationInfo: { sessionId: string; token: string; providerId: string } | undefined; + private static CACHED_SESSION_STORAGE_KEY = 'editSessionAccountPreference'; + + private initialized = false; + private readonly signedInContext: IContextKey; + + constructor( + @IFileService private readonly fileService: IFileService, + @IStorageService private readonly storageService: IStorageService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IAuthenticationService private readonly authenticationService: IAuthenticationService, + @IExtensionService private readonly extensionService: IExtensionService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IEditSessionsLogService private readonly logService: IEditSessionsLogService, + @IProductService private readonly productService: IProductService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IRequestService private readonly requestService: IRequestService, + ) { + super(); + + // If the user signs out of the current session, reset our cached auth state in memory and on disk + this._register(this.authenticationService.onDidChangeSessions((e) => this.onDidChangeSessions(e.event))); + + // If another window changes the preferred session storage, reset our cached auth state in memory + this._register(this.storageService.onDidChangeValue(e => this.onDidChangeStorage(e))); + + this.registerResetAuthenticationAction(); + + this.signedInContext = EDIT_SESSIONS_SIGNED_IN.bindTo(this.contextKeyService); + this.signedInContext.set(this.existingSessionId !== undefined); + } + + /** + * + * @param editSession An object representing edit session state to be restored. + * @returns The ref of the stored edit session state. + */ + async write(editSession: EditSession): Promise { + await this.initialize(); + if (!this.initialized) { + throw new Error('Please sign in to store your edit session.'); + } + + return this.storeClient!.write('editSessions', JSON.stringify(editSession), null); + } + + /** + * @param ref: A specific content ref to retrieve content for, if it exists. + * If undefined, this method will return the latest saved edit session, if any. + * + * @returns An object representing the requested or latest edit session state, if any. + */ + async read(ref: string | undefined): Promise<{ ref: string; editSession: EditSession } | undefined> { + await this.initialize(); + if (!this.initialized) { + throw new Error('Please sign in to apply your latest edit session.'); + } + + let content: string | undefined | null; + try { + if (ref !== undefined) { + content = await this.storeClient?.resolveContent('editSessions', ref); + } else { + const result = await this.storeClient?.read('editSessions', null); + content = result?.content; + ref = result?.ref; + } + } catch (ex) { + this.logService.error(ex); + } + + // TODO@joyceerhl Validate session data, check schema version + return (content !== undefined && content !== null && ref !== undefined) ? { ref: ref, editSession: JSON.parse(content) } : undefined; + } + + async delete(ref: string) { + await this.initialize(); + if (!this.initialized) { + throw new Error(`Unable to delete edit session with ref ${ref}.`); + } + + try { + await this.storeClient?.delete('editSessions', ref); + } catch (ex) { + this.logService.error(ex); + } + } + + private async initialize() { + if (this.initialized) { + return; + } + this.initialized = await this.doInitialize(); + this.signedInContext.set(this.initialized); + } + + /** + * + * Ensures that the store client is initialized, + * meaning that authentication is configured and it + * can be used to communicate with the remote storage service + */ + private async doInitialize(): Promise { + // Wait for authentication extensions to be registered + await this.extensionService.whenInstalledExtensionsRegistered(); + + if (!this.serverConfiguration?.url) { + throw new Error('Unable to initialize sessions sync as session sync preference is not configured in product.json.'); + } + + if (!this.storeClient) { + this.storeClient = new UserDataSyncStoreClient(URI.parse(this.serverConfiguration.url), this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService); + this._register(this.storeClient.onTokenFailed(() => { + this.logService.info('Clearing edit sessions authentication preference because of successive token failures.'); + this.clearAuthenticationPreference(); + })); + } + + // If we already have an existing auth session in memory, use that + if (this.#authenticationInfo !== undefined) { + return true; + } + + // If the user signed in previously and the session is still available, reuse that without prompting the user again + const existingSessionId = this.existingSessionId; + if (existingSessionId) { + this.logService.trace(`Searching for existing authentication session with ID ${existingSessionId}`); + const existing = await this.getExistingSession(); + if (existing !== undefined) { + this.logService.trace(`Found existing authentication session with ID ${existingSessionId}`); + this.#authenticationInfo = { sessionId: existing.session.id, token: existing.session.accessToken, providerId: existing.session.providerId }; + this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId); + return true; + } + } + + // Ask the user to pick a preferred account + const session = await this.getAccountPreference(); + if (session !== undefined) { + this.#authenticationInfo = { sessionId: session.id, token: session.accessToken, providerId: session.providerId }; + this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId); + this.existingSessionId = session.id; + this.logService.trace(`Saving authentication session preference for ID ${session.id}.`); + return true; + } + + return false; + } + + /** + * + * Prompts the user to pick an authentication option for storing and getting edit sessions. + */ + private async getAccountPreference(): Promise { + const quickpick = this.quickInputService.createQuickPick(); + quickpick.title = localize('account preference', 'Sign In to Use Edit Sessions'); + quickpick.ok = false; + quickpick.placeholder = localize('choose account placeholder', "Select an account to sign in"); + quickpick.ignoreFocusOut = true; + quickpick.items = await this.createQuickpickItems(); + + return new Promise((resolve, reject) => { + quickpick.onDidHide((e) => { + resolve(undefined); + quickpick.dispose(); + }); + + quickpick.onDidAccept(async (e) => { + const selection = quickpick.selectedItems[0]; + const session = 'provider' in selection ? { ...await this.authenticationService.createSession(selection.provider.id, selection.provider.scopes), providerId: selection.provider.id } : selection.session; + resolve(session); + quickpick.hide(); + }); + + quickpick.show(); + }); + } + + private async createQuickpickItems(): Promise<(ExistingSession | AuthenticationProviderOption | IQuickPickSeparator)[]> { + const options: (ExistingSession | AuthenticationProviderOption | IQuickPickSeparator)[] = []; + + options.push({ type: 'separator', label: localize('signed in', "Signed In") }); + + const sessions = await this.getAllSessions(); + options.push(...sessions); + + options.push({ type: 'separator', label: localize('others', "Others") }); + + for (const authenticationProvider of (await this.getAuthenticationProviders())) { + const signedInForProvider = sessions.some(account => account.session.providerId === authenticationProvider.id); + if (!signedInForProvider || this.authenticationService.supportsMultipleAccounts(authenticationProvider.id)) { + const providerName = this.authenticationService.getLabel(authenticationProvider.id); + options.push({ label: localize('sign in using account', "Sign in with {0}", providerName), provider: authenticationProvider }); + } + } + + return options; + } + + /** + * + * Returns all authentication sessions available from {@link getAuthenticationProviders}. + */ + private async getAllSessions() { + const authenticationProviders = await this.getAuthenticationProviders(); + const accounts = new Map(); + let currentSession: ExistingSession | undefined; + + for (const provider of authenticationProviders) { + const sessions = await this.authenticationService.getSessions(provider.id, provider.scopes); + + for (const session of sessions) { + const item = { + label: session.account.label, + description: this.authenticationService.getLabel(provider.id), + session: { ...session, providerId: provider.id } + }; + accounts.set(item.session.account.id, item); + if (this.existingSessionId === session.id) { + currentSession = item; + } + } + } + + if (currentSession !== undefined) { + accounts.set(currentSession.session.account.id, currentSession); + } + + return [...accounts.values()]; + } + + /** + * + * Returns all authentication providers which can be used to authenticate + * to the remote storage service, based on product.json configuration + * and registered authentication providers. + */ + private async getAuthenticationProviders() { + if (!this.serverConfiguration) { + throw new Error('Unable to get configured authentication providers as session sync preference is not configured in product.json.'); + } + + // Get the list of authentication providers configured in product.json + const authenticationProviders = this.serverConfiguration.authenticationProviders; + const configuredAuthenticationProviders = Object.keys(authenticationProviders).reduce((result, id) => { + result.push({ id, scopes: authenticationProviders[id].scopes }); + return result; + }, []); + + // Filter out anything that isn't currently available through the authenticationService + const availableAuthenticationProviders = this.authenticationService.declaredProviders; + + return configuredAuthenticationProviders.filter(({ id }) => availableAuthenticationProviders.some(provider => provider.id === id)); + } + + private get existingSessionId() { + return this.storageService.get(EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION); + } + + private set existingSessionId(sessionId: string | undefined) { + if (sessionId === undefined) { + this.storageService.remove(EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION); + } else { + this.storageService.store(EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY, sessionId, StorageScope.APPLICATION, StorageTarget.MACHINE); + } + } + + private async getExistingSession() { + const accounts = await this.getAllSessions(); + return accounts.find((account) => account.session.id === this.existingSessionId); + } + + private async onDidChangeStorage(e: IStorageValueChangeEvent): Promise { + if (e.key === EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY + && e.scope === StorageScope.APPLICATION + ) { + const newSessionId = this.existingSessionId; + const previousSessionId = this.#authenticationInfo?.sessionId; + + if (previousSessionId !== newSessionId) { + this.logService.trace(`Resetting authentication state because authentication session ID preference changed from ${previousSessionId} to ${newSessionId}.`); + this.#authenticationInfo = undefined; + this.initialized = false; + } + } + } + + private clearAuthenticationPreference(): void { + this.#authenticationInfo = undefined; + this.initialized = false; + this.existingSessionId = undefined; + this.signedInContext.set(false); + } + + private onDidChangeSessions(e: AuthenticationSessionsChangeEvent): void { + if (this.#authenticationInfo?.sessionId && e.removed.find(session => session.id === this.#authenticationInfo?.sessionId)) { + this.clearAuthenticationPreference(); + } + } + + private registerResetAuthenticationAction() { + const that = this; + this._register(registerAction2(class ResetEditSessionAuthenticationAction extends Action2 { + constructor() { + super({ + id: 'workbench.editSessions.actions.resetAuth', + title: localize('reset auth', 'Sign Out'), + category: EDIT_SESSION_SYNC_CATEGORY, + precondition: ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, true), + menu: [{ + id: MenuId.CommandPalette, + }, + { + id: MenuId.AccountsContext, + group: '2_editSessions', + when: ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, true), + }] + }); + } + + run() { + that.clearAuthenticationPreference(); + } + })); + } +} diff --git a/src/vs/workbench/contrib/editSessions/common/editSessions.ts b/src/vs/workbench/contrib/editSessions/common/editSessions.ts new file mode 100644 index 00000000000..789976a50e5 --- /dev/null +++ b/src/vs/workbench/contrib/editSessions/common/editSessions.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { ILocalizedString } from 'vs/platform/action/common/action'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; + +export const EDIT_SESSION_SYNC_CATEGORY: ILocalizedString = { + original: 'Edit Sessions', + value: localize('session sync', 'Edit Sessions') +}; + +export const IEditSessionsWorkbenchService = createDecorator('IEditSessionsWorkbenchService'); +export interface IEditSessionsWorkbenchService { + _serviceBrand: undefined; + + read(ref: string | undefined): Promise<{ ref: string; editSession: EditSession } | undefined>; + write(editSession: EditSession): Promise; + delete(ref: string): Promise; +} + +export const IEditSessionsLogService = createDecorator('IEditSessionsLogService'); +export interface IEditSessionsLogService extends ILogService { } + +export enum ChangeType { + Addition = 1, + Deletion = 2, +} + +export enum FileType { + File = 1, +} + +interface Addition { + relativeFilePath: string; + fileType: FileType.File; + contents: string; + type: ChangeType.Addition; +} + +interface Deletion { + relativeFilePath: string; + fileType: FileType.File; + contents: undefined; + type: ChangeType.Deletion; +} + +export type Change = Addition | Deletion; + +export interface Folder { + name: string; + workingChanges: Change[]; +} + +export const EditSessionSchemaVersion = 1; + +export interface EditSession { + version: number; + folders: Folder[]; +} + +export const EDIT_SESSIONS_SIGNED_IN_KEY = 'editSessionsSignedIn'; +export const EDIT_SESSIONS_SIGNED_IN = new RawContextKey(EDIT_SESSIONS_SIGNED_IN_KEY, false); diff --git a/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts b/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts new file mode 100644 index 00000000000..a18c9e29304 --- /dev/null +++ b/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { AbstractLogger, ILogger, ILoggerService } from 'vs/platform/log/common/log'; +import { IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions'; + +export class EditSessionsLogService extends AbstractLogger implements IEditSessionsLogService { + + declare readonly _serviceBrand: undefined; + private readonly logger: ILogger; + + constructor( + @ILoggerService loggerService: ILoggerService, + @IEnvironmentService environmentService: IEnvironmentService + ) { + super(); + this.logger = this._register(loggerService.createLogger(environmentService.editSessionsLogResource, { name: 'editsessions' })); + } + + trace(message: string, ...args: any[]): void { + this.logger.trace(message, ...args); + } + + debug(message: string, ...args: any[]): void { + this.logger.debug(message, ...args); + } + + info(message: string, ...args: any[]): void { + this.logger.info(message, ...args); + } + + warn(message: string, ...args: any[]): void { + this.logger.warn(message, ...args); + } + + error(message: string | Error, ...args: any[]): void { + this.logger.error(message, ...args); + } + + critical(message: string | Error, ...args: any[]): void { + this.logger.critical(message, ...args); + } + + flush(): void { + this.logger.flush(); + } +} diff --git a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts new file mode 100644 index 00000000000..b7caca6f077 --- /dev/null +++ b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts @@ -0,0 +1,134 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IFileService } from 'vs/platform/files/common/files'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { Schemas } from 'vs/base/common/network'; +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { EditSessionsContribution } from 'vs/workbench/contrib/editSessions/browser/editSessions.contribution'; +import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; +import { IProgressService } from 'vs/platform/progress/common/progress'; +import { ISCMService } from 'vs/workbench/contrib/scm/common/scm'; +import { SCMService } from 'vs/workbench/contrib/scm/common/scmService'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { mock } from 'vs/base/test/common/mock'; +import * as sinon from 'sinon'; +import * as assert from 'assert'; +import { ChangeType, FileType, IEditSessionsLogService, IEditSessionsWorkbenchService } from 'vs/workbench/contrib/editSessions/common/editSessions'; +import { URI } from 'vs/base/common/uri'; +import { joinPath } from 'vs/base/common/resources'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; +import { TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; + +const folderName = 'test-folder'; +const folderUri = URI.file(`/${folderName}`); + +suite('Edit session sync', () => { + let instantiationService: TestInstantiationService; + let editSessionsContribution: EditSessionsContribution; + let fileService: FileService; + let sandbox: sinon.SinonSandbox; + + const disposables = new DisposableStore(); + + suiteSetup(() => { + sandbox = sinon.createSandbox(); + + instantiationService = new TestInstantiationService(); + + // Set up filesystem + const logService = new NullLogService(); + fileService = disposables.add(new FileService(logService)); + const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); + fileService.registerProvider(Schemas.file, fileSystemProvider); + + // Stub out all services + instantiationService.stub(IEditSessionsLogService, logService); + instantiationService.stub(IFileService, fileService); + instantiationService.stub(INotificationService, new TestNotificationService()); + instantiationService.stub(IEditSessionsWorkbenchService, new class extends mock() { }); + instantiationService.stub(IProgressService, ProgressService); + instantiationService.stub(ISCMService, SCMService); + instantiationService.stub(IEnvironmentService, TestEnvironmentService); + instantiationService.stub(IConfigurationService, new TestConfigurationService({ workbench: { experimental: { editSessions: { enabled: true } } } })); + instantiationService.stub(IWorkspaceContextService, new class extends mock() { + override getWorkspace() { + return { + id: 'workspace-id', + folders: [{ + uri: folderUri, + name: folderName, + index: 0, + toResource: (relativePath: string) => joinPath(folderUri, relativePath) + }] + }; + } + }); + + // Stub repositories + instantiationService.stub(ISCMService, '_repositories', new Map()); + + editSessionsContribution = instantiationService.createInstance(EditSessionsContribution); + }); + + teardown(() => { + sinon.restore(); + disposables.clear(); + }); + + test('Can apply edit session', async function () { + const fileUri = joinPath(folderUri, 'dir1', 'README.md'); + const fileContents = '# readme'; + const editSession = { + version: 1, + folders: [ + { + name: folderName, + workingChanges: [ + { + relativeFilePath: 'dir1/README.md', + fileType: FileType.File, + contents: fileContents, + type: ChangeType.Addition + } + ] + } + ] + }; + + // Stub sync service to return edit session data + const readStub = sandbox.stub().returns({ editSession, ref: '0' }); + instantiationService.stub(IEditSessionsWorkbenchService, 'read', readStub); + + // Create root folder + await fileService.createFolder(folderUri); + + // Apply edit session + await editSessionsContribution.applyEditSession(); + + // Verify edit session was correctly applied + assert.equal((await fileService.readFile(fileUri)).value.toString(), fileContents); + }); + + test('Edit session not stored if there are no edits', async function () { + const writeStub = sandbox.stub(); + instantiationService.stub(IEditSessionsWorkbenchService, 'write', writeStub); + + // Create root folder + await fileService.createFolder(folderUri); + + await editSessionsContribution.storeEditSession(true); + + // Verify that we did not attempt to write the edit session + assert.equal(writeStub.called, false); + }); +}); diff --git a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts b/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts deleted file mode 100644 index a4cf8be1f5f..00000000000 --- a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts +++ /dev/null @@ -1,517 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable } from 'vs/base/common/lifecycle'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { Action2, IAction2Options, registerAction2 } from 'vs/platform/actions/common/actions'; -import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { localize } from 'vs/nls'; -import { ISessionSyncWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EditSessionSchemaVersion, IEditSessionsLogService } from 'vs/workbench/contrib/sessionSync/common/sessionSync'; -import { ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { URI } from 'vs/base/common/uri'; -import { joinPath, relativePath } from 'vs/base/common/resources'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { SessionSyncWorkbenchService } from 'vs/workbench/contrib/sessionSync/browser/sessionSyncWorkbenchService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { UserDataSyncErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; -import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtualWorkspace'; -import { Schemas } from 'vs/base/common/network'; -import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; -import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; -import { EditSessionsLogService } from 'vs/workbench/contrib/sessionSync/common/editSessionsLogService'; - -registerSingleton(IEditSessionsLogService, EditSessionsLogService); -registerSingleton(ISessionSyncWorkbenchService, SessionSyncWorkbenchService); - -const continueEditSessionCommand: IAction2Options = { - id: '_workbench.experimental.editSessions.actions.continueEditSession', - title: { value: localize('continue edit session', "Continue Edit Session..."), original: 'Continue Edit Session...' }, - category: EDIT_SESSION_SYNC_CATEGORY, - f1: true -}; -const openLocalFolderCommand: IAction2Options = { - id: '_workbench.experimental.editSessions.actions.continueEditSession.openLocalFolder', - title: { value: localize('continue edit session in local folder', "Open In Local Folder"), original: 'Open In Local Folder' }, - category: EDIT_SESSION_SYNC_CATEGORY, - precondition: IsWebContext -}; -const queryParamName = 'editSessionId'; -const experimentalSettingName = 'workbench.experimental.editSessions.enabled'; - -export class SessionSyncContribution extends Disposable implements IWorkbenchContribution { - - private registered = false; - private continueEditSessionOptions: ContinueEditSessionItem[] = []; - - constructor( - @ISessionSyncWorkbenchService private readonly sessionSyncWorkbenchService: ISessionSyncWorkbenchService, - @IFileService private readonly fileService: IFileService, - @IProgressService private readonly progressService: IProgressService, - @IOpenerService private readonly openerService: IOpenerService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - @ISCMService private readonly scmService: ISCMService, - @INotificationService private readonly notificationService: INotificationService, - @IDialogService private readonly dialogService: IDialogService, - @IEditSessionsLogService private readonly logService: IEditSessionsLogService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IProductService private readonly productService: IProductService, - @IConfigurationService private configurationService: IConfigurationService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @ICommandService private commandService: ICommandService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IFileDialogService private readonly fileDialogService: IFileDialogService - ) { - super(); - - if (this.environmentService.editSessionId !== undefined) { - void this.applyEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined); - } - - this.configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration(experimentalSettingName)) { - this.registerActions(); - } - }); - - this.registerActions(); - - continueEditSessionExtPoint.setHandler(extensions => { - const continueEditSessionOptions: ContinueEditSessionItem[] = []; - for (const extension of extensions) { - if (!isProposedApiEnabled(extension.description, 'contribEditSessions')) { - continue; - } - if (!Array.isArray(extension.value)) { - continue; - } - const commands = new Map((extension.description.contributes?.commands ?? []).map(c => [c.command, c])); - for (const contribution of extension.value) { - if (!contribution.command || !contribution.group || !contribution.when) { - continue; - } - const fullCommand = commands.get(contribution.command); - if (!fullCommand) { return; } - - continueEditSessionOptions.push(new ContinueEditSessionItem( - fullCommand.title, - fullCommand.command, - ContextKeyExpr.deserialize(contribution.when) - )); - } - } - this.continueEditSessionOptions = continueEditSessionOptions; - }); - } - - private registerActions() { - if (this.registered || this.configurationService.getValue(experimentalSettingName) !== true) { - this.logService.info(`Skipping registering edit sessions actions as edit sessions are currently disabled. Set ${experimentalSettingName} to enable edit sessions.`); - return; - } - - this.registerContinueEditSessionAction(); - - this.registerApplyLatestEditSessionAction(); - this.registerStoreLatestEditSessionAction(); - - this.registerContinueInLocalFolderAction(); - - this.registered = true; - } - - private registerContinueEditSessionAction() { - const that = this; - this._register(registerAction2(class ContinueEditSessionAction extends Action2 { - constructor() { - super(continueEditSessionCommand); - } - - async run(accessor: ServicesAccessor, workspaceUri: URI | undefined): Promise { - let uri = workspaceUri ?? await that.pickContinueEditSessionDestination(); - if (uri === undefined) { return; } - - // Run the store action to get back a ref - const ref = await that.storeEditSession(false); - - // Append the ref to the URI - if (ref !== undefined) { - const encodedRef = encodeURIComponent(ref); - uri = uri.with({ - query: uri.query.length > 0 ? (uri + `&${queryParamName}=${encodedRef}`) : `${queryParamName}=${encodedRef}` - }); - } else { - that.logService.warn(`Failed to store edit session when invoking ${continueEditSessionCommand.id}.`); - } - - // Open the URI - that.logService.info(`Opening ${uri.toString()}`); - await that.openerService.open(uri, { openExternal: true }); - } - })); - } - - private registerApplyLatestEditSessionAction(): void { - const that = this; - this._register(registerAction2(class ApplyLatestEditSessionAction extends Action2 { - constructor() { - super({ - id: 'workbench.experimental.editSessions.actions.resumeLatest', - title: { value: localize('resume latest.v2', "Resume Latest Edit Session"), original: 'Resume Latest Edit Session' }, - category: EDIT_SESSION_SYNC_CATEGORY, - f1: true, - }); - } - - async run(accessor: ServicesAccessor): Promise { - await that.progressService.withProgress({ - location: ProgressLocation.Notification, - title: localize('applying edit session', 'Applying edit session...') - }, async () => await that.applyEditSession()); - } - })); - } - - private registerStoreLatestEditSessionAction(): void { - const that = this; - this._register(registerAction2(class StoreLatestEditSessionAction extends Action2 { - constructor() { - super({ - id: 'workbench.experimental.editSessions.actions.storeCurrent', - title: { value: localize('store current.v2', "Store Current Edit Session"), original: 'Store Current Edit Session' }, - category: EDIT_SESSION_SYNC_CATEGORY, - f1: true, - }); - } - - async run(accessor: ServicesAccessor): Promise { - await that.progressService.withProgress({ - location: ProgressLocation.Notification, - title: localize('storing edit session', 'Storing edit session...') - }, async () => await that.storeEditSession(true)); - } - })); - } - - async applyEditSession(ref?: string): Promise { - if (ref !== undefined) { - this.logService.info(`Applying edit session with ref ${ref}.`); - } - - const data = await this.sessionSyncWorkbenchService.read(ref); - if (!data) { - if (ref === undefined) { - this.notificationService.info(localize('no edit session', 'There are no edit sessions to apply.')); - } else { - this.notificationService.warn(localize('no edit session content for ref', 'Could not apply edit session contents for ID {0}.', ref)); - } - this.logService.info(`Aborting applying edit session as no edit session content is available to be applied from ref ${ref}.`); - return; - } - const editSession = data.editSession; - ref = data.ref; - - if (editSession.version > EditSessionSchemaVersion) { - this.notificationService.error(localize('client too old', "Please upgrade to a newer version of {0} to apply this edit session.", this.productService.nameLong)); - return; - } - - try { - const changes: ({ uri: URI; type: ChangeType; contents: string | undefined })[] = []; - let hasLocalUncommittedChanges = false; - - for (const folder of editSession.folders) { - const folderRoot = this.contextService.getWorkspace().folders.find((f) => f.name === folder.name); - if (!folderRoot) { - this.logService.info(`Skipping applying ${folder.workingChanges.length} changes from edit session with ref ${ref} as no corresponding workspace folder named ${folder.name} is currently open.`); - continue; - } - - for (const repository of this.scmService.repositories) { - if (repository.provider.rootUri !== undefined && - this.contextService.getWorkspaceFolder(repository.provider.rootUri)?.name === folder.name && - this.getChangedResources(repository).length > 0 - ) { - hasLocalUncommittedChanges = true; - break; - } - } - - for (const { relativeFilePath, contents, type } of folder.workingChanges) { - const uri = joinPath(folderRoot.uri, relativeFilePath); - changes.push({ uri: uri, type: type, contents: contents }); - } - } - - if (hasLocalUncommittedChanges) { - // TODO@joyceerhl Provide the option to diff files which would be overwritten by edit session contents - const result = await this.dialogService.confirm({ - message: localize('apply edit session warning', 'Applying your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'), - type: 'warning', - title: EDIT_SESSION_SYNC_CATEGORY.value - }); - if (!result.confirmed) { - return; - } - } - - for (const { uri, type, contents } of changes) { - if (type === ChangeType.Addition) { - await this.fileService.writeFile(uri, VSBuffer.fromString(contents!)); - } else if (type === ChangeType.Deletion && await this.fileService.exists(uri)) { - await this.fileService.del(uri); - } - } - - this.logService.info(`Deleting edit session with ref ${ref} after successfully applying it to current workspace...`); - await this.sessionSyncWorkbenchService.delete(ref); - this.logService.info(`Deleted edit session with ref ${ref}.`); - } catch (ex) { - this.logService.error('Failed to apply edit session, reason: ', (ex as Error).toString()); - this.notificationService.error(localize('apply failed', "Failed to apply your edit session.")); - } - } - - async storeEditSession(fromStoreCommand: boolean): Promise { - const folders: Folder[] = []; - let hasEdits = false; - - for (const repository of this.scmService.repositories) { - // Look through all resource groups and compute which files were added/modified/deleted - const trackedUris = this.getChangedResources(repository); // A URI might appear in more than one resource group - - const workingChanges: Change[] = []; - let name = repository.provider.rootUri ? this.contextService.getWorkspaceFolder(repository.provider.rootUri)?.name : undefined; - - for (const uri of trackedUris) { - const workspaceFolder = this.contextService.getWorkspaceFolder(uri); - if (!workspaceFolder) { - this.logService.info(`Skipping working change ${uri.toString()} as no associated workspace folder was found.`); - - continue; - } - - name = name ?? workspaceFolder.name; - const relativeFilePath = relativePath(workspaceFolder.uri, uri) ?? uri.path; - - // Only deal with file contents for now - try { - if (!(await this.fileService.stat(uri)).isFile) { - continue; - } - } catch { } - - hasEdits = true; - - if (await this.fileService.exists(uri)) { - workingChanges.push({ type: ChangeType.Addition, fileType: FileType.File, contents: (await this.fileService.readFile(uri)).value.toString(), relativeFilePath: relativeFilePath }); - } else { - // Assume it's a deletion - workingChanges.push({ type: ChangeType.Deletion, fileType: FileType.File, contents: undefined, relativeFilePath: relativeFilePath }); - } - } - - folders.push({ workingChanges, name: name ?? '' }); - } - - if (!hasEdits) { - this.logService.info('Skipping storing edit session as there are no edits to store.'); - if (fromStoreCommand) { - this.notificationService.info(localize('no edits to store', 'Skipped storing edit session as there are no edits to store.')); - } - return undefined; - } - - const data: EditSession = { folders, version: 1 }; - - try { - this.logService.info(`Storing edit session...`); - const ref = await this.sessionSyncWorkbenchService.write(data); - this.logService.info(`Stored edit session with ref ${ref}.`); - return ref; - } catch (ex) { - this.logService.error(`Failed to store edit session, reason: `, (ex as Error).toString()); - - type UploadFailedEvent = { reason: string }; - type UploadFailedClassification = { - owner: 'joyceerhl'; comment: 'Reporting when Continue On server request fails.'; - reason?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The reason that the server request failed.' }; - }; - - if (ex instanceof UserDataSyncStoreError) { - switch (ex.code) { - case UserDataSyncErrorCode.TooLarge: - // Uploading a payload can fail due to server size limits - this.telemetryService.publicLog2('sessionSync.upload.failed', { reason: 'TooLarge' }); - this.notificationService.error(localize('payload too large', 'Your edit session exceeds the size limit and cannot be stored.')); - break; - default: - this.telemetryService.publicLog2('sessionSync.upload.failed', { reason: 'unknown' }); - this.notificationService.error(localize('payload failed', 'Your edit session cannot be stored.')); - break; - } - } - } - - return undefined; - } - - private getChangedResources(repository: ISCMRepository) { - const trackedUris = repository.provider.groups.elements.reduce((resources, resourceGroups) => { - resourceGroups.elements.forEach((resource) => resources.add(resource.sourceUri)); - return resources; - }, new Set()); // A URI might appear in more than one resource group - - return [...trackedUris]; - } - - //#region Continue Edit Session extension contribution point - - private registerContinueInLocalFolderAction(): void { - const that = this; - this._register(registerAction2(class ContinueInLocalFolderAction extends Action2 { - constructor() { - super(openLocalFolderCommand); - } - - async run(accessor: ServicesAccessor): Promise { - const selection = await that.fileDialogService.showOpenDialog({ - title: localize('continueEditSession.openLocalFolder.title', 'Select a local folder to continue your edit session in'), - canSelectFolders: true, - canSelectMany: false, - canSelectFiles: false, - availableFileSystems: [Schemas.file] - }); - - return selection?.length !== 1 ? undefined : URI.from({ - scheme: that.productService.urlProtocol, - authority: Schemas.file, - path: selection[0].path - }); - } - })); - } - - private async pickContinueEditSessionDestination(): Promise { - const quickPick = this.quickInputService.createQuickPick(); - - quickPick.title = localize('continueEditSessionPick.title', 'Continue Edit Session...'); - quickPick.placeholder = localize('continueEditSessionPick.placeholder', 'Choose how you would like to continue working'); - quickPick.items = this.createPickItems(); - - const command = await new Promise((resolve, reject) => { - quickPick.onDidHide(() => resolve(undefined)); - - quickPick.onDidAccept((e) => { - const selection = quickPick.activeItems[0].command; - resolve(selection); - quickPick.hide(); - }); - - quickPick.show(); - }); - - quickPick.dispose(); - - if (command === undefined) { - return undefined; - } - - try { - const uri = await this.commandService.executeCommand(command); - return URI.isUri(uri) ? uri : undefined; - } catch (ex) { - return undefined; - } - } - - private createPickItems(): ContinueEditSessionItem[] { - const items = [...this.continueEditSessionOptions].filter((option) => option.when === undefined || this.contextKeyService.contextMatchesRules(option.when)); - - if (getVirtualWorkspaceLocation(this.contextService.getWorkspace()) !== undefined) { - items.push(new ContinueEditSessionItem( - localize('continueEditSessionItem.openInLocalFolder', 'Open In Local Folder'), - openLocalFolderCommand.id, - )); - } - - return items; - } -} - -class ContinueEditSessionItem implements IQuickPickItem { - constructor( - public readonly label: string, - public readonly command: string, - public readonly when?: ContextKeyExpression, - ) { } -} - -interface ICommand { - command: string; - group: string; - when: string; -} - -const continueEditSessionExtPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'continueEditSession', - jsonSchema: { - description: localize('continueEditSessionExtPoint', 'Contributes options for continuing the current edit session in a different environment'), - type: 'array', - items: { - type: 'object', - properties: { - command: { - description: localize('continueEditSessionExtPoint.command', 'Identifier of the command to execute. The command must be declared in the \'commands\'-section and return a URI representing a different environment where the current edit session can be continued.'), - type: 'string' - }, - group: { - description: localize('continueEditSessionExtPoint.group', 'Group into which this item belongs.'), - type: 'string' - }, - when: { - description: localize('continueEditSessionExtPoint.when', 'Condition which must be true to show this item.'), - type: 'string' - } - }, - required: ['command'] - } - } -}); - -//#endregion - -const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchRegistry.registerWorkbenchContribution(SessionSyncContribution, LifecyclePhase.Restored); - -Registry.as(Extensions.Configuration).registerConfiguration({ - ...workbenchConfigurationNodeBase, - 'properties': { - 'workbench.experimental.editSessions.enabled': { - 'type': 'boolean', - 'tags': ['experimental', 'usesOnlineServices'], - 'default': false, - 'markdownDescription': localize('editSessionsEnabled', "Controls whether to display cloud-enabled actions to store and resume uncommitted changes when switching between web, desktop, or devices."), - }, - } -}); diff --git a/src/vs/workbench/contrib/sessionSync/browser/sessionSyncWorkbenchService.ts b/src/vs/workbench/contrib/sessionSync/browser/sessionSyncWorkbenchService.ts deleted file mode 100644 index 1df0ecc3d28..00000000000 --- a/src/vs/workbench/contrib/sessionSync/browser/sessionSyncWorkbenchService.ts +++ /dev/null @@ -1,358 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; -import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IRequestService } from 'vs/platform/request/common/request'; -import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync'; -import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, ISessionSyncWorkbenchService, EDIT_SESSIONS_SIGNED_IN_KEY, IEditSessionsLogService } from 'vs/workbench/contrib/sessionSync/common/sessionSync'; - -type ExistingSession = IQuickPickItem & { session: AuthenticationSession & { providerId: string } }; -type AuthenticationProviderOption = IQuickPickItem & { provider: IAuthenticationProvider }; - -export class SessionSyncWorkbenchService extends Disposable implements ISessionSyncWorkbenchService { - - _serviceBrand = undefined; - - private serverConfiguration = this.productService['sessionSync.store']; - private storeClient: UserDataSyncStoreClient | undefined; - - #authenticationInfo: { sessionId: string; token: string; providerId: string } | undefined; - private static CACHED_SESSION_STORAGE_KEY = 'editSessionSyncAccountPreference'; - - private initialized = false; - private readonly signedInContext: IContextKey; - - constructor( - @IFileService private readonly fileService: IFileService, - @IStorageService private readonly storageService: IStorageService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @IAuthenticationService private readonly authenticationService: IAuthenticationService, - @IExtensionService private readonly extensionService: IExtensionService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IEditSessionsLogService private readonly logService: IEditSessionsLogService, - @IProductService private readonly productService: IProductService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IRequestService private readonly requestService: IRequestService, - ) { - super(); - - // If the user signs out of the current session, reset our cached auth state in memory and on disk - this._register(this.authenticationService.onDidChangeSessions((e) => this.onDidChangeSessions(e.event))); - - // If another window changes the preferred session storage, reset our cached auth state in memory - this._register(this.storageService.onDidChangeValue(e => this.onDidChangeStorage(e))); - - this.registerResetAuthenticationAction(); - - this.signedInContext = EDIT_SESSIONS_SIGNED_IN.bindTo(this.contextKeyService); - this.signedInContext.set(this.existingSessionId !== undefined); - } - - /** - * - * @param editSession An object representing edit session state to be restored. - * @returns The ref of the stored edit session state. - */ - async write(editSession: EditSession): Promise { - await this.initialize(); - if (!this.initialized) { - throw new Error('Please sign in to store your edit session.'); - } - - return this.storeClient!.write('editSessions', JSON.stringify(editSession), null); - } - - /** - * @param ref: A specific content ref to retrieve content for, if it exists. - * If undefined, this method will return the latest saved edit session, if any. - * - * @returns An object representing the requested or latest edit session state, if any. - */ - async read(ref: string | undefined): Promise<{ ref: string; editSession: EditSession } | undefined> { - await this.initialize(); - if (!this.initialized) { - throw new Error('Please sign in to apply your latest edit session.'); - } - - let content: string | undefined | null; - try { - if (ref !== undefined) { - content = await this.storeClient?.resolveContent('editSessions', ref); - } else { - const result = await this.storeClient?.read('editSessions', null); - content = result?.content; - ref = result?.ref; - } - } catch (ex) { - this.logService.error(ex); - } - - // TODO@joyceerhl Validate session data, check schema version - return (content !== undefined && content !== null && ref !== undefined) ? { ref: ref, editSession: JSON.parse(content) } : undefined; - } - - async delete(ref: string) { - await this.initialize(); - if (!this.initialized) { - throw new Error(`Unable to delete edit session with ref ${ref}.`); - } - - try { - await this.storeClient?.delete('editSessions', ref); - } catch (ex) { - this.logService.error(ex); - } - } - - private async initialize() { - if (this.initialized) { - return; - } - this.initialized = await this.doInitialize(); - this.signedInContext.set(this.initialized); - } - - /** - * - * Ensures that the store client is initialized, - * meaning that authentication is configured and it - * can be used to communicate with the remote storage service - */ - private async doInitialize(): Promise { - // Wait for authentication extensions to be registered - await this.extensionService.whenInstalledExtensionsRegistered(); - - if (!this.serverConfiguration?.url) { - throw new Error('Unable to initialize sessions sync as session sync preference is not configured in product.json.'); - } - - if (!this.storeClient) { - this.storeClient = new UserDataSyncStoreClient(URI.parse(this.serverConfiguration.url), this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService); - this._register(this.storeClient.onTokenFailed(() => { - this.logService.info('Clearing edit sessions authentication preference because of successive token failures.'); - this.clearAuthenticationPreference(); - })); - } - - // If we already have an existing auth session in memory, use that - if (this.#authenticationInfo !== undefined) { - return true; - } - - // If the user signed in previously and the session is still available, reuse that without prompting the user again - const existingSessionId = this.existingSessionId; - if (existingSessionId) { - this.logService.trace(`Searching for existing authentication session with ID ${existingSessionId}`); - const existing = await this.getExistingSession(); - if (existing !== undefined) { - this.logService.trace(`Found existing authentication session with ID ${existingSessionId}`); - this.#authenticationInfo = { sessionId: existing.session.id, token: existing.session.accessToken, providerId: existing.session.providerId }; - this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId); - return true; - } - } - - // Ask the user to pick a preferred account - const session = await this.getAccountPreference(); - if (session !== undefined) { - this.#authenticationInfo = { sessionId: session.id, token: session.accessToken, providerId: session.providerId }; - this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId); - this.existingSessionId = session.id; - this.logService.trace(`Saving authentication session preference for ID ${session.id}.`); - return true; - } - - return false; - } - - /** - * - * Prompts the user to pick an authentication option for storing and getting edit sessions. - */ - private async getAccountPreference(): Promise { - const quickpick = this.quickInputService.createQuickPick(); - quickpick.title = localize('account preference', 'Sign In to Use Edit Sessions'); - quickpick.ok = false; - quickpick.placeholder = localize('choose account placeholder', "Select an account to sign in"); - quickpick.ignoreFocusOut = true; - quickpick.items = await this.createQuickpickItems(); - - return new Promise((resolve, reject) => { - quickpick.onDidHide((e) => { - resolve(undefined); - quickpick.dispose(); - }); - - quickpick.onDidAccept(async (e) => { - const selection = quickpick.selectedItems[0]; - const session = 'provider' in selection ? { ...await this.authenticationService.createSession(selection.provider.id, selection.provider.scopes), providerId: selection.provider.id } : selection.session; - resolve(session); - quickpick.hide(); - }); - - quickpick.show(); - }); - } - - private async createQuickpickItems(): Promise<(ExistingSession | AuthenticationProviderOption | IQuickPickSeparator)[]> { - const options: (ExistingSession | AuthenticationProviderOption | IQuickPickSeparator)[] = []; - - options.push({ type: 'separator', label: localize('signed in', "Signed In") }); - - const sessions = await this.getAllSessions(); - options.push(...sessions); - - options.push({ type: 'separator', label: localize('others', "Others") }); - - for (const authenticationProvider of (await this.getAuthenticationProviders())) { - const signedInForProvider = sessions.some(account => account.session.providerId === authenticationProvider.id); - if (!signedInForProvider || this.authenticationService.supportsMultipleAccounts(authenticationProvider.id)) { - const providerName = this.authenticationService.getLabel(authenticationProvider.id); - options.push({ label: localize('sign in using account', "Sign in with {0}", providerName), provider: authenticationProvider }); - } - } - - return options; - } - - /** - * - * Returns all authentication sessions available from {@link getAuthenticationProviders}. - */ - private async getAllSessions() { - const authenticationProviders = await this.getAuthenticationProviders(); - const accounts = new Map(); - let currentSession: ExistingSession | undefined; - - for (const provider of authenticationProviders) { - const sessions = await this.authenticationService.getSessions(provider.id, provider.scopes); - - for (const session of sessions) { - const item = { - label: session.account.label, - description: this.authenticationService.getLabel(provider.id), - session: { ...session, providerId: provider.id } - }; - accounts.set(item.session.account.id, item); - if (this.existingSessionId === session.id) { - currentSession = item; - } - } - } - - if (currentSession !== undefined) { - accounts.set(currentSession.session.account.id, currentSession); - } - - return [...accounts.values()]; - } - - /** - * - * Returns all authentication providers which can be used to authenticate - * to the remote storage service, based on product.json configuration - * and registered authentication providers. - */ - private async getAuthenticationProviders() { - if (!this.serverConfiguration) { - throw new Error('Unable to get configured authentication providers as session sync preference is not configured in product.json.'); - } - - // Get the list of authentication providers configured in product.json - const authenticationProviders = this.serverConfiguration.authenticationProviders; - const configuredAuthenticationProviders = Object.keys(authenticationProviders).reduce((result, id) => { - result.push({ id, scopes: authenticationProviders[id].scopes }); - return result; - }, []); - - // Filter out anything that isn't currently available through the authenticationService - const availableAuthenticationProviders = this.authenticationService.declaredProviders; - - return configuredAuthenticationProviders.filter(({ id }) => availableAuthenticationProviders.some(provider => provider.id === id)); - } - - private get existingSessionId() { - return this.storageService.get(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION); - } - - private set existingSessionId(sessionId: string | undefined) { - if (sessionId === undefined) { - this.storageService.remove(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION); - } else { - this.storageService.store(SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY, sessionId, StorageScope.APPLICATION, StorageTarget.MACHINE); - } - } - - private async getExistingSession() { - const accounts = await this.getAllSessions(); - return accounts.find((account) => account.session.id === this.existingSessionId); - } - - private async onDidChangeStorage(e: IStorageValueChangeEvent): Promise { - if (e.key === SessionSyncWorkbenchService.CACHED_SESSION_STORAGE_KEY - && e.scope === StorageScope.APPLICATION - ) { - const newSessionId = this.existingSessionId; - const previousSessionId = this.#authenticationInfo?.sessionId; - - if (previousSessionId !== newSessionId) { - this.logService.trace(`Resetting authentication state because authentication session ID preference changed from ${previousSessionId} to ${newSessionId}.`); - this.#authenticationInfo = undefined; - this.initialized = false; - } - } - } - - private clearAuthenticationPreference(): void { - this.#authenticationInfo = undefined; - this.initialized = false; - this.existingSessionId = undefined; - this.signedInContext.set(false); - } - - private onDidChangeSessions(e: AuthenticationSessionsChangeEvent): void { - if (this.#authenticationInfo?.sessionId && e.removed.find(session => session.id === this.#authenticationInfo?.sessionId)) { - this.clearAuthenticationPreference(); - } - } - - private registerResetAuthenticationAction() { - const that = this; - this._register(registerAction2(class ResetEditSessionAuthenticationAction extends Action2 { - constructor() { - super({ - id: 'workbench.sessionSync.actions.resetAuth', - title: localize('reset auth', 'Sign Out'), - category: EDIT_SESSION_SYNC_CATEGORY, - precondition: ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, true), - menu: [{ - id: MenuId.CommandPalette, - }, - { - id: MenuId.AccountsContext, - group: '2_editSessions', - when: ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, true), - }] - }); - } - - run() { - that.clearAuthenticationPreference(); - } - })); - } -} diff --git a/src/vs/workbench/contrib/sessionSync/common/editSessionsLogService.ts b/src/vs/workbench/contrib/sessionSync/common/editSessionsLogService.ts deleted file mode 100644 index 2b3b6bca671..00000000000 --- a/src/vs/workbench/contrib/sessionSync/common/editSessionsLogService.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { AbstractLogger, ILogger, ILoggerService } from 'vs/platform/log/common/log'; -import { IEditSessionsLogService } from 'vs/workbench/contrib/sessionSync/common/sessionSync'; - -export class EditSessionsLogService extends AbstractLogger implements IEditSessionsLogService { - - declare readonly _serviceBrand: undefined; - private readonly logger: ILogger; - - constructor( - @ILoggerService loggerService: ILoggerService, - @IEnvironmentService environmentService: IEnvironmentService - ) { - super(); - this.logger = this._register(loggerService.createLogger(environmentService.editSessionsLogResource, { name: 'editsessions' })); - } - - trace(message: string, ...args: any[]): void { - this.logger.trace(message, ...args); - } - - debug(message: string, ...args: any[]): void { - this.logger.debug(message, ...args); - } - - info(message: string, ...args: any[]): void { - this.logger.info(message, ...args); - } - - warn(message: string, ...args: any[]): void { - this.logger.warn(message, ...args); - } - - error(message: string | Error, ...args: any[]): void { - this.logger.error(message, ...args); - } - - critical(message: string | Error, ...args: any[]): void { - this.logger.critical(message, ...args); - } - - flush(): void { - this.logger.flush(); - } -} diff --git a/src/vs/workbench/contrib/sessionSync/common/sessionSync.ts b/src/vs/workbench/contrib/sessionSync/common/sessionSync.ts deleted file mode 100644 index 538a54a6444..00000000000 --- a/src/vs/workbench/contrib/sessionSync/common/sessionSync.ts +++ /dev/null @@ -1,67 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { localize } from 'vs/nls'; -import { ILocalizedString } from 'vs/platform/action/common/action'; -import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { ILogService } from 'vs/platform/log/common/log'; - -export const EDIT_SESSION_SYNC_CATEGORY: ILocalizedString = { - original: 'Edit Sessions', - value: localize('session sync', 'Edit Sessions') -}; - -export const ISessionSyncWorkbenchService = createDecorator('ISessionSyncWorkbenchService'); -export interface ISessionSyncWorkbenchService { - _serviceBrand: undefined; - - read(ref: string | undefined): Promise<{ ref: string; editSession: EditSession } | undefined>; - write(editSession: EditSession): Promise; - delete(ref: string): Promise; -} - -export const IEditSessionsLogService = createDecorator('IEditSessionsLogService'); -export interface IEditSessionsLogService extends ILogService { } - -export enum ChangeType { - Addition = 1, - Deletion = 2, -} - -export enum FileType { - File = 1, -} - -interface Addition { - relativeFilePath: string; - fileType: FileType.File; - contents: string; - type: ChangeType.Addition; -} - -interface Deletion { - relativeFilePath: string; - fileType: FileType.File; - contents: undefined; - type: ChangeType.Deletion; -} - -export type Change = Addition | Deletion; - -export interface Folder { - name: string; - workingChanges: Change[]; -} - -export const EditSessionSchemaVersion = 1; - -export interface EditSession { - version: number; - folders: Folder[]; -} - -export const EDIT_SESSIONS_SIGNED_IN_KEY = 'editSessionsSignedIn'; -export const EDIT_SESSIONS_SIGNED_IN = new RawContextKey(EDIT_SESSIONS_SIGNED_IN_KEY, false); diff --git a/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts b/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts deleted file mode 100644 index 140c068d5d0..00000000000 --- a/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IFileService } from 'vs/platform/files/common/files'; -import { FileService } from 'vs/platform/files/common/fileService'; -import { Schemas } from 'vs/base/common/network'; -import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; -import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { NullLogService } from 'vs/platform/log/common/log'; -import { SessionSyncContribution } from 'vs/workbench/contrib/sessionSync/browser/sessionSync.contribution'; -import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; -import { IProgressService } from 'vs/platform/progress/common/progress'; -import { ISCMService } from 'vs/workbench/contrib/scm/common/scm'; -import { SCMService } from 'vs/workbench/contrib/scm/common/scmService'; -import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { mock } from 'vs/base/test/common/mock'; -import * as sinon from 'sinon'; -import * as assert from 'assert'; -import { ChangeType, FileType, IEditSessionsLogService, ISessionSyncWorkbenchService } from 'vs/workbench/contrib/sessionSync/common/sessionSync'; -import { URI } from 'vs/base/common/uri'; -import { joinPath } from 'vs/base/common/resources'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; -import { TestEnvironmentService } from 'vs/workbench/test/browser/workbenchTestServices'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; - -const folderName = 'test-folder'; -const folderUri = URI.file(`/${folderName}`); - -suite('Edit session sync', () => { - let instantiationService: TestInstantiationService; - let sessionSyncContribution: SessionSyncContribution; - let fileService: FileService; - let sandbox: sinon.SinonSandbox; - - const disposables = new DisposableStore(); - - suiteSetup(() => { - sandbox = sinon.createSandbox(); - - instantiationService = new TestInstantiationService(); - - // Set up filesystem - const logService = new NullLogService(); - fileService = disposables.add(new FileService(logService)); - const fileSystemProvider = disposables.add(new InMemoryFileSystemProvider()); - fileService.registerProvider(Schemas.file, fileSystemProvider); - - // Stub out all services - instantiationService.stub(IEditSessionsLogService, logService); - instantiationService.stub(IFileService, fileService); - instantiationService.stub(INotificationService, new TestNotificationService()); - instantiationService.stub(ISessionSyncWorkbenchService, new class extends mock() { }); - instantiationService.stub(IProgressService, ProgressService); - instantiationService.stub(ISCMService, SCMService); - instantiationService.stub(IEnvironmentService, TestEnvironmentService); - instantiationService.stub(IConfigurationService, new TestConfigurationService({ workbench: { experimental: { sessionSync: { enabled: true } } } })); - instantiationService.stub(IWorkspaceContextService, new class extends mock() { - override getWorkspace() { - return { - id: 'workspace-id', - folders: [{ - uri: folderUri, - name: folderName, - index: 0, - toResource: (relativePath: string) => joinPath(folderUri, relativePath) - }] - }; - } - }); - - // Stub repositories - instantiationService.stub(ISCMService, '_repositories', new Map()); - - sessionSyncContribution = instantiationService.createInstance(SessionSyncContribution); - }); - - teardown(() => { - sinon.restore(); - disposables.clear(); - }); - - test('Can apply edit session', async function () { - const fileUri = joinPath(folderUri, 'dir1', 'README.md'); - const fileContents = '# readme'; - const editSession = { - version: 1, - folders: [ - { - name: folderName, - workingChanges: [ - { - relativeFilePath: 'dir1/README.md', - fileType: FileType.File, - contents: fileContents, - type: ChangeType.Addition - } - ] - } - ] - }; - - // Stub sync service to return edit session data - const readStub = sandbox.stub().returns({ editSession, ref: '0' }); - instantiationService.stub(ISessionSyncWorkbenchService, 'read', readStub); - - // Create root folder - await fileService.createFolder(folderUri); - - // Apply edit session - await sessionSyncContribution.applyEditSession(); - - // Verify edit session was correctly applied - assert.equal((await fileService.readFile(fileUri)).value.toString(), fileContents); - }); - - test('Edit session not stored if there are no edits', async function () { - const writeStub = sandbox.stub(); - instantiationService.stub(ISessionSyncWorkbenchService, 'write', writeStub); - - // Create root folder - await fileService.createFolder(folderUri); - - await sessionSyncContribution.storeEditSession(true); - - // Verify that we did not attempt to write the edit session - assert.equal(writeStub.called, false); - }); -}); -- cgit v1.2.3 From 0c12daf1faed6f26b9742aec0bcaf27b55b4bf20 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 12:30:25 -0700 Subject: Make run recent command use contiguous filtering Part of #154016 --- .../contrib/terminal/browser/terminalInstance.ts | 81 +++++++++++++--------- 1 file changed, 50 insertions(+), 31 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index edf4d2b4198..bff1bea1b1e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -965,42 +965,61 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } const outputProvider = this._instantiationService.createInstance(TerminalOutputProvider); const quickPick = this._quickInputService.createQuickPick(); - quickPick.items = items; + const originalItems = items; + quickPick.items = [...originalItems]; quickPick.sortByLabel = false; + quickPick.matchOnLabel = false; quickPick.placeholder = placeholder; - return new Promise(r => { - quickPick.onDidTriggerItemButton(async e => { - if (e.button === removeFromCommandHistoryButton) { - if (type === 'command') { - this._instantiationService.invokeFunction(getCommandHistory)?.remove(e.item.label); - } else { - this._instantiationService.invokeFunction(getDirectoryHistory)?.remove(e.item.label); - } + quickPick.title = 'Run Recent Command'; + quickPick.onDidChangeValue(value => { + quickPick.items = originalItems.filter(item => { + if (item.type === 'separator') { + return true; + } + item.highlights = undefined; + const matchIndex = item.label.indexOf(value); + if (matchIndex !== -1) { + item.highlights = { + label: [{ start: matchIndex, end: matchIndex + value.length }] + }; + return true; + } + return false; + }); + }); + quickPick.onDidTriggerItemButton(async e => { + if (e.button === removeFromCommandHistoryButton) { + if (type === 'command') { + this._instantiationService.invokeFunction(getCommandHistory)?.remove(e.item.label); } else { - const selectedCommand = (e.item as Item).command; - const output = selectedCommand?.getOutput(); - if (output && selectedCommand?.command) { - const textContent = await outputProvider.provideTextContent(URI.from( - { - scheme: TerminalOutputProvider.scheme, - path: `${selectedCommand.command}... ${fromNow(selectedCommand.timestamp, true)}`, - fragment: output, - query: `terminal-output-${selectedCommand.timestamp}-${this.instanceId}` - })); - if (textContent) { - await this._editorService.openEditor({ - resource: textContent.uri - }); - } + this._instantiationService.invokeFunction(getDirectoryHistory)?.remove(e.item.label); + } + } else { + const selectedCommand = (e.item as Item).command; + const output = selectedCommand?.getOutput(); + if (output && selectedCommand?.command) { + const textContent = await outputProvider.provideTextContent(URI.from( + { + scheme: TerminalOutputProvider.scheme, + path: `${selectedCommand.command}... ${fromNow(selectedCommand.timestamp, true)}`, + fragment: output, + query: `terminal-output-${selectedCommand.timestamp}-${this.instanceId}` + })); + if (textContent) { + await this._editorService.openEditor({ + resource: textContent.uri + }); } } - quickPick.hide(); - }); - quickPick.onDidAccept(() => { - const result = quickPick.activeItems[0]; - this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, !quickPick.keyMods.alt); - quickPick.hide(); - }); + } + quickPick.hide(); + }); + quickPick.onDidAccept(() => { + const result = quickPick.activeItems[0]; + this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, !quickPick.keyMods.alt); + quickPick.hide(); + }); + return new Promise(r => { quickPick.show(); quickPick.onDidHide(() => r()); }); -- cgit v1.2.3 From 61cf9c4316e22113d0b27a316366f4784cfa8514 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 12:40:12 -0700 Subject: Allow use of contiguous or fuzzy search in run recent --- .../contrib/terminal/browser/terminalInstance.ts | 49 ++++++++++++++-------- 1 file changed, 32 insertions(+), 17 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index bff1bea1b1e..ddd4ffc2729 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -818,7 +818,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._linkManager.openRecentLink(type); } - async runRecent(type: 'command' | 'cwd'): Promise { + async runRecent(type: 'command' | 'cwd', filterMode?: 'fuzzy' | 'contiguous'): Promise { if (!this.xterm) { return; } @@ -968,25 +968,40 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const originalItems = items; quickPick.items = [...originalItems]; quickPick.sortByLabel = false; - quickPick.matchOnLabel = false; quickPick.placeholder = placeholder; quickPick.title = 'Run Recent Command'; - quickPick.onDidChangeValue(value => { - quickPick.items = originalItems.filter(item => { - if (item.type === 'separator') { - return true; - } - item.highlights = undefined; - const matchIndex = item.label.indexOf(value); - if (matchIndex !== -1) { - item.highlights = { - label: [{ start: matchIndex, end: matchIndex + value.length }] - }; - return true; - } - return false; + quickPick.customButton = true; + quickPick.matchOnLabel = filterMode === 'fuzzy'; + if (filterMode === 'fuzzy') { + quickPick.customLabel = nls.localize('terminal.contiguousSearch', 'Use Contiguous Search'); + quickPick.onDidCustom(() => { + quickPick.hide(); + this.runRecent(type, 'contiguous'); }); - }); + } else { + // contiguous is the default for command + quickPick.onDidChangeValue(value => { + quickPick.items = originalItems.filter(item => { + if (item.type === 'separator') { + return true; + } + item.highlights = undefined; + const matchIndex = item.label.indexOf(value); + if (matchIndex !== -1) { + item.highlights = { + label: [{ start: matchIndex, end: matchIndex + value.length }] + }; + return true; + } + return false; + }); + }); + quickPick.customLabel = nls.localize('terminal.fuzzySearch', 'Use Fuzzy Search'); + quickPick.onDidCustom(() => { + quickPick.hide(); + this.runRecent(type, 'fuzzy'); + }); + } quickPick.onDidTriggerItemButton(async e => { if (e.button === removeFromCommandHistoryButton) { if (type === 'command') { -- cgit v1.2.3 From 6fc821730afb4b00ae23ca71c99b127eb0492f2d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 12:42:53 -0700 Subject: Carry value over to new search --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index ddd4ffc2729..2e47d3c4867 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -818,7 +818,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._linkManager.openRecentLink(type); } - async runRecent(type: 'command' | 'cwd', filterMode?: 'fuzzy' | 'contiguous'): Promise { + async runRecent(type: 'command' | 'cwd', filterMode?: 'fuzzy' | 'contiguous', value?: string): Promise { if (!this.xterm) { return; } @@ -969,14 +969,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { quickPick.items = [...originalItems]; quickPick.sortByLabel = false; quickPick.placeholder = placeholder; - quickPick.title = 'Run Recent Command'; quickPick.customButton = true; quickPick.matchOnLabel = filterMode === 'fuzzy'; if (filterMode === 'fuzzy') { quickPick.customLabel = nls.localize('terminal.contiguousSearch', 'Use Contiguous Search'); quickPick.onDidCustom(() => { quickPick.hide(); - this.runRecent(type, 'contiguous'); + this.runRecent(type, 'contiguous', quickPick.value); }); } else { // contiguous is the default for command @@ -999,7 +998,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { quickPick.customLabel = nls.localize('terminal.fuzzySearch', 'Use Fuzzy Search'); quickPick.onDidCustom(() => { quickPick.hide(); - this.runRecent(type, 'fuzzy'); + this.runRecent(type, 'fuzzy', quickPick.value); }); } quickPick.onDidTriggerItemButton(async e => { @@ -1034,6 +1033,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, !quickPick.keyMods.alt); quickPick.hide(); }); + if (value) { + quickPick.value = value; + } return new Promise(r => { quickPick.show(); quickPick.onDidHide(() => r()); -- cgit v1.2.3 From 465e8b4e57244d90257e57c6158a4f56eeb0d6e9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 12:54:45 -0700 Subject: Move fuzzy/contiguous filtering into quick pick --- .../contrib/terminal/browser/terminalInstance.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 2e47d3c4867..f0e24670756 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -970,7 +970,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { quickPick.sortByLabel = false; quickPick.placeholder = placeholder; quickPick.customButton = true; - quickPick.matchOnLabel = filterMode === 'fuzzy'; + quickPick.matchOnLabelMode = filterMode || 'contiguous'; if (filterMode === 'fuzzy') { quickPick.customLabel = nls.localize('terminal.contiguousSearch', 'Use Contiguous Search'); quickPick.onDidCustom(() => { @@ -978,23 +978,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this.runRecent(type, 'contiguous', quickPick.value); }); } else { - // contiguous is the default for command - quickPick.onDidChangeValue(value => { - quickPick.items = originalItems.filter(item => { - if (item.type === 'separator') { - return true; - } - item.highlights = undefined; - const matchIndex = item.label.indexOf(value); - if (matchIndex !== -1) { - item.highlights = { - label: [{ start: matchIndex, end: matchIndex + value.length }] - }; - return true; - } - return false; - }); - }); quickPick.customLabel = nls.localize('terminal.fuzzySearch', 'Use Fuzzy Search'); quickPick.onDidCustom(() => { quickPick.hide(); -- cgit v1.2.3 From 419a7cc1a0bd6be79b27e80eaf7e0dfbb2dd6543 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 6 Jul 2022 13:13:57 -0700 Subject: Register commands correctly with original title, #153865 (#154297) --- .../browser/contrib/cellCommands/cellCommands.ts | 90 +++++++++++++++++----- .../contrib/search/browser/search.contribution.ts | 25 ++++-- 2 files changed, 92 insertions(+), 23 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts index 2adc303d4e1..5074d759b06 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts @@ -31,7 +31,10 @@ registerAction2(class extends NotebookCellAction { super( { id: MOVE_CELL_UP_COMMAND_ID, - title: localize('notebookActions.moveCellUp', "Move Cell Up"), + title: { + value: localize('notebookActions.moveCellUp', "Move Cell Up"), + original: 'Move Cell Up' + }, icon: icons.moveUpIcon, keybinding: { primary: KeyMod.Alt | KeyCode.UpArrow, @@ -57,7 +60,10 @@ registerAction2(class extends NotebookCellAction { super( { id: MOVE_CELL_DOWN_COMMAND_ID, - title: localize('notebookActions.moveCellDown', "Move Cell Down"), + title: { + value: localize('notebookActions.moveCellDown', "Move Cell Down"), + original: 'Move Cell Down' + }, icon: icons.moveDownIcon, keybinding: { primary: KeyMod.Alt | KeyCode.DownArrow, @@ -83,7 +89,10 @@ registerAction2(class extends NotebookCellAction { super( { id: COPY_CELL_UP_COMMAND_ID, - title: localize('notebookActions.copyCellUp', "Copy Cell Up"), + title: { + value: localize('notebookActions.copyCellUp', "Copy Cell Up"), + original: 'Copy Cell Up' + }, keybinding: { primary: KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow, when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), @@ -102,7 +111,10 @@ registerAction2(class extends NotebookCellAction { super( { id: COPY_CELL_DOWN_COMMAND_ID, - title: localize('notebookActions.copyCellDown', "Copy Cell Down"), + title: { + value: localize('notebookActions.copyCellDown', "Copy Cell Down"), + original: 'Copy Cell Down' + }, keybinding: { primary: KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow, when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()), @@ -137,7 +149,10 @@ registerAction2(class extends NotebookCellAction { super( { id: SPLIT_CELL_COMMAND_ID, - title: localize('notebookActions.splitCell', "Split Cell"), + title: { + value: localize('notebookActions.splitCell', "Split Cell"), + original: 'Split Cell' + }, menu: { id: MenuId.NotebookCellTitle, when: ContextKeyExpr.and( @@ -212,7 +227,10 @@ registerAction2(class extends NotebookCellAction { super( { id: JOIN_CELL_ABOVE_COMMAND_ID, - title: localize('notebookActions.joinCellAbove', "Join With Previous Cell"), + title: { + value: localize('notebookActions.joinCellAbove', "Join With Previous Cell"), + original: 'Join With Previous Cell' + }, keybinding: { when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyJ, @@ -238,7 +256,10 @@ registerAction2(class extends NotebookCellAction { super( { id: JOIN_CELL_BELOW_COMMAND_ID, - title: localize('notebookActions.joinCellBelow', "Join With Next Cell"), + title: { + value: localize('notebookActions.joinCellBelow', "Join With Next Cell"), + original: 'Join With Next Cell' + }, keybinding: { when: NOTEBOOK_EDITOR_FOCUSED, primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyJ, @@ -270,7 +291,10 @@ registerAction2(class ChangeCellToCodeAction extends NotebookMultiCellAction { constructor() { super({ id: CHANGE_CELL_TO_CODE_COMMAND_ID, - title: localize('notebookActions.changeCellToCode', "Change Cell to Code"), + title: { + value: localize('notebookActions.changeCellToCode', "Change Cell to Code"), + original: 'Change Cell to Code' + }, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), primary: KeyCode.KeyY, @@ -294,7 +318,10 @@ registerAction2(class ChangeCellToMarkdownAction extends NotebookMultiCellAction constructor() { super({ id: CHANGE_CELL_TO_MARKDOWN_COMMAND_ID, - title: localize('notebookActions.changeCellToMarkdown', "Change Cell to Markdown"), + title: { + value: localize('notebookActions.changeCellToMarkdown', "Change Cell to Markdown"), + original: 'Change Cell to Markdown' + }, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)), primary: KeyCode.KeyM, @@ -330,7 +357,10 @@ registerAction2(class CollapseCellInputAction extends NotebookMultiCellAction { constructor() { super({ id: COLLAPSE_CELL_INPUT_COMMAND_ID, - title: localize('notebookActions.collapseCellInput', "Collapse Cell Input"), + title: { + value: localize('notebookActions.collapseCellInput', "Collapse Cell Input"), + original: 'Collapse Cell Input' + }, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_INPUT_COLLAPSED.toNegated(), InputFocusedContext.toNegated()), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC), @@ -356,7 +386,10 @@ registerAction2(class ExpandCellInputAction extends NotebookMultiCellAction { constructor() { super({ id: EXPAND_CELL_INPUT_COMMAND_ID, - title: localize('notebookActions.expandCellInput', "Expand Cell Input"), + title: { + value: localize('notebookActions.expandCellInput', "Expand Cell Input"), + original: 'Expand Cell Input' + }, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_INPUT_COLLAPSED), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC), @@ -382,7 +415,10 @@ registerAction2(class CollapseCellOutputAction extends NotebookMultiCellAction { constructor() { super({ id: COLLAPSE_CELL_OUTPUT_COMMAND_ID, - title: localize('notebookActions.collapseCellOutput', "Collapse Cell Output"), + title: { + value: localize('notebookActions.collapseCellOutput', "Collapse Cell Output"), + original: 'Collapse Cell Output' + }, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated(), InputFocusedContext.toNegated(), NOTEBOOK_CELL_HAS_OUTPUTS), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyT), @@ -404,7 +440,10 @@ registerAction2(class ExpandCellOuputAction extends NotebookMultiCellAction { constructor() { super({ id: EXPAND_CELL_OUTPUT_COMMAND_ID, - title: localize('notebookActions.expandCellOutput', "Expand Cell Output"), + title: { + value: localize('notebookActions.expandCellOutput', "Expand Cell Output"), + original: 'Expand Cell Output' + }, keybinding: { when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED), primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyT), @@ -427,7 +466,10 @@ registerAction2(class extends NotebookMultiCellAction { super({ id: TOGGLE_CELL_OUTPUTS_COMMAND_ID, precondition: NOTEBOOK_CELL_LIST_FOCUSED, - title: localize('notebookActions.toggleOutputs', "Toggle Outputs"), + title: { + value: localize('notebookActions.toggleOutputs', "Toggle Outputs"), + original: 'Toggle Outputs' + }, description: { description: localize('notebookActions.toggleOutputs', "Toggle Outputs"), args: cellExecutionArgs @@ -457,7 +499,10 @@ registerAction2(class CollapseAllCellInputsAction extends NotebookMultiCellActio constructor() { super({ id: COLLAPSE_ALL_CELL_INPUTS_COMMAND_ID, - title: localize('notebookActions.collapseAllCellInput', "Collapse All Cell Inputs"), + title: { + value: localize('notebookActions.collapseAllCellInput', "Collapse All Cell Inputs"), + original: 'Collapse All Cell Inputs' + }, f1: true, }); } @@ -471,7 +516,10 @@ registerAction2(class ExpandAllCellInputsAction extends NotebookMultiCellAction constructor() { super({ id: EXPAND_ALL_CELL_INPUTS_COMMAND_ID, - title: localize('notebookActions.expandAllCellInput', "Expand All Cell Inputs"), + title: { + value: localize('notebookActions.expandAllCellInput', "Expand All Cell Inputs"), + original: 'Expand All Cell Inputs' + }, f1: true }); } @@ -485,7 +533,10 @@ registerAction2(class CollapseAllCellOutputsAction extends NotebookMultiCellActi constructor() { super({ id: COLLAPSE_ALL_CELL_OUTPUTS_COMMAND_ID, - title: localize('notebookActions.collapseAllCellOutput', "Collapse All Cell Outputs"), + title: { + value: localize('notebookActions.collapseAllCellOutput', "Collapse All Cell Outputs"), + original: 'Collapse All Cell Outputs' + }, f1: true, }); } @@ -499,7 +550,10 @@ registerAction2(class ExpandAllCellOutputsAction extends NotebookMultiCellAction constructor() { super({ id: EXPAND_ALL_CELL_OUTPUTS_COMMAND_ID, - title: localize('notebookActions.expandAllCellOutput', "Expand All Cell Outputs"), + title: { + value: localize('notebookActions.expandAllCellOutput', "Expand All Cell Outputs"), + original: 'Expand All Cell Outputs' + }, f1: true }); } diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 77782e09059..08f7dc2cf25 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -365,7 +365,10 @@ registerAction2(class CancelSearchAction extends Action2 { constructor() { super({ id: 'search.action.cancel', - title: nls.localize('CancelSearchAction.label', "Cancel Search"), + title: { + value: nls.localize('CancelSearchAction.label', "Cancel Search"), + original: 'Cancel Search' + }, icon: searchStopIcon, category, f1: true, @@ -392,7 +395,10 @@ registerAction2(class RefreshAction extends Action2 { constructor() { super({ id: 'search.action.refreshSearchResults', - title: nls.localize('RefreshAction.label', "Refresh"), + title: { + value: nls.localize('RefreshAction.label', "Refresh"), + original: 'Refresh' + }, icon: searchRefreshIcon, precondition: Constants.ViewHasSearchPatternKey, category, @@ -414,7 +420,10 @@ registerAction2(class CollapseDeepestExpandedLevelAction extends Action2 { constructor() { super({ id: 'search.action.collapseSearchResults', - title: nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"), + title: { + value: nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"), + original: 'Collapse All' + }, category, icon: searchCollapseAllIcon, f1: true, @@ -436,7 +445,10 @@ registerAction2(class ExpandAllAction extends Action2 { constructor() { super({ id: 'search.action.expandSearchResults', - title: nls.localize('ExpandAllAction.label', "Expand All"), + title: { + value: nls.localize('ExpandAllAction.label', "Expand All"), + original: 'Expand All' + }, category, icon: searchExpandAllIcon, f1: true, @@ -458,7 +470,10 @@ registerAction2(class ClearSearchResultsAction extends Action2 { constructor() { super({ id: 'search.action.clearSearchResults', - title: nls.localize('ClearSearchResultsAction.label', "Clear Search Results"), + title: { + value: nls.localize('ClearSearchResultsAction.label', "Clear Search Results"), + original: 'Clear Search Results' + }, category, icon: searchClearIcon, f1: true, -- cgit v1.2.3 From 9c724118aea8f0c4539c745c0474371fdfe21038 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 6 Jul 2022 13:50:18 -0700 Subject: re #153865. ICommandActionTitle for notebook (#154307) --- .../contrib/gettingStarted/notebookGettingStarted.ts | 5 ++++- .../notebook/browser/contrib/troubleshoot/layout.ts | 16 +++++++++++++--- .../notebook/browser/controller/layoutActions.ts | 20 ++++++++++++++++---- 3 files changed, 33 insertions(+), 8 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts b/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts index 3e1c47af18a..2165fa94cc2 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts @@ -81,7 +81,10 @@ registerAction2(class NotebookClearNotebookLayoutAction extends Action2 { constructor() { super({ id: 'workbench.notebook.layout.gettingStarted', - title: localize('workbench.notebook.layout.gettingStarted.label', "Reset notebook getting started"), + title: { + value: localize('workbench.notebook.layout.gettingStarted.label', "Reset notebook getting started"), + original: 'Reset notebook getting started' + }, f1: true, precondition: ContextKeyExpr.equals(`config.${NotebookSetting.openGettingStarted}`, true), category: CATEGORIES.Developer, diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts index b3af4143741..ab714f85b51 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { CATEGORIES } from 'vs/workbench/common/actions'; @@ -121,7 +122,10 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.toggleLayoutTroubleshoot', - title: 'Toggle Notebook Layout Troubleshoot', + title: { + value: localize('workbench.notebook.toggleLayoutTroubleshoot', "Toggle Layout Troubleshoot"), + original: 'Toggle Notebook Layout Troubleshoot' + }, category: CATEGORIES.Developer, f1: true }); @@ -144,7 +148,10 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.inspectLayout', - title: 'Inspect Notebook Layout', + title: { + value: localize('workbench.notebook.inspectLayout', "Inspect Notebook Layout"), + original: 'Inspect Notebook Layout' + }, category: CATEGORIES.Developer, f1: true }); @@ -169,7 +176,10 @@ registerAction2(class extends Action2 { constructor() { super({ id: 'notebook.clearNotebookEdtitorTypeCache', - title: 'Clear Notebook Editor Cache', + title: { + value: localize('workbench.notebook.clearNotebookEdtitorTypeCache', "Clear Notebook Editor Type Cache"), + original: 'Clear Notebook Editor Cache' + }, category: CATEGORIES.Developer, f1: true }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts index 4f6a3e489c5..7f6ac0a645b 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts @@ -21,7 +21,10 @@ registerAction2(class NotebookConfigureLayoutAction extends Action2 { constructor() { super({ id: 'workbench.notebook.layout.select', - title: localize('workbench.notebook.layout.select.label', "Select between Notebook Layouts"), + title: { + value: localize('workbench.notebook.layout.select.label', "Select between Notebook Layouts"), + original: 'Select between Notebook Layouts' + }, f1: true, precondition: ContextKeyExpr.equals(`config.${NotebookSetting.openGettingStarted}`, true), category: NOTEBOOK_ACTIONS_CATEGORY, @@ -57,7 +60,10 @@ registerAction2(class NotebookConfigureLayoutAction extends Action2 { constructor() { super({ id: 'workbench.notebook.layout.configure', - title: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"), + title: { + value: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"), + original: 'Customize Notebook Layout' + }, f1: true, category: NOTEBOOK_ACTIONS_CATEGORY, menu: [ @@ -79,7 +85,10 @@ registerAction2(class NotebookConfigureLayoutFromEditorTitle extends Action2 { constructor() { super({ id: 'workbench.notebook.layout.configure.editorTitle', - title: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"), + title: { + value: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"), + original: 'Customize Notebook Layout' + }, f1: false, category: NOTEBOOK_ACTIONS_CATEGORY, menu: [ @@ -177,7 +186,10 @@ registerAction2(class SaveMimeTypeDisplayOrder extends Action2 { constructor() { super({ id: 'notebook.saveMimeTypeOrder', - title: localize('notebook.saveMimeTypeOrder', 'Save Mimetype Display Order'), + title: { + value: localize('notebook.saveMimeTypeOrder', 'Save Mimetype Display Order'), + original: 'Save Mimetype Display Order' + }, f1: true, category: NOTEBOOK_ACTIONS_CATEGORY, precondition: NOTEBOOK_IS_ACTIVE_EDITOR, -- cgit v1.2.3 From 3d3bfced969d0ffd1fdc7a1938cc26bbd64d9d42 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Wed, 6 Jul 2022 15:46:56 -0700 Subject: Commands for Navigating Call Stack (#154117) Commands to navigate the call stack Fixes #149975 --- .../contrib/debug/browser/debug.contribution.ts | 7 +- .../contrib/debug/browser/debugCommands.ts | 140 ++++++++++++++++++++- .../contrib/debug/browser/debugSession.ts | 2 +- src/vs/workbench/contrib/debug/common/debug.ts | 2 + .../workbench/contrib/debug/common/debugModel.ts | 26 +++- 5 files changed, 172 insertions(+), 5 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index bebcecd9455..54d0f0a0e7c 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -20,7 +20,7 @@ import { } from 'vs/workbench/contrib/debug/common/debug'; import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar'; import { DebugService } from 'vs/workbench/contrib/debug/browser/debugService'; -import { ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SET_EXPRESSION_COMMAND_ID, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, SHOW_LOADED_SCRIPTS_ID, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, STEP_INTO_TARGET_LABEL, STEP_INTO_TARGET_ID, DEBUG_COMMAND_CATEGORY } from 'vs/workbench/contrib/debug/browser/debugCommands'; +import { ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SET_EXPRESSION_COMMAND_ID, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, SHOW_LOADED_SCRIPTS_ID, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, STEP_INTO_TARGET_LABEL, STEP_INTO_TARGET_ID, CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CALLSTACK_BOTTOM_LABEL, CALLSTACK_UP_LABEL, CALLSTACK_BOTTOM_ID, CALLSTACK_UP_ID, CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, DEBUG_COMMAND_CATEGORY } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; import { isMacintosh, isWeb } from 'vs/base/common/platform'; @@ -136,7 +136,10 @@ registerDebugCommandPaletteItem(NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL) registerDebugCommandPaletteItem(PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL); registerDebugCommandPaletteItem(SHOW_LOADED_SCRIPTS_ID, OPEN_LOADED_SCRIPTS_LABEL, CONTEXT_IN_DEBUG_MODE); registerDebugCommandPaletteItem(SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL); - +registerDebugCommandPaletteItem(CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugCommandPaletteItem(CALLSTACK_BOTTOM_ID, CALLSTACK_BOTTOM_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugCommandPaletteItem(CALLSTACK_UP_ID, CALLSTACK_UP_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugCommandPaletteItem(CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); // Debug callstack context menu const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string | ICommandActionTitle, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation', icon?: Icon) => { diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 86fa5b8c01b..25da3464ac6 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -9,7 +9,7 @@ import { List } from 'vs/base/browser/ui/list/listWidget'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IListService } from 'vs/platform/list/browser/listService'; import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, REPL_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, State, getStateLabel, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, VIEWLET_ID, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_IN_DEBUG_REPL, CONTEXT_STEP_INTO_TARGETS_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug'; -import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; @@ -64,6 +64,10 @@ export const REMOVE_EXPRESSION_COMMAND_ID = 'debug.removeWatchExpression'; export const NEXT_DEBUG_CONSOLE_ID = 'workbench.action.debug.nextConsole'; export const PREV_DEBUG_CONSOLE_ID = 'workbench.action.debug.prevConsole'; export const SHOW_LOADED_SCRIPTS_ID = 'workbench.action.debug.showLoadedScripts'; +export const CALLSTACK_TOP_ID = 'workbench.action.debug.callStackTop'; +export const CALLSTACK_BOTTOM_ID = 'workbench.action.debug.callStackBottom'; +export const CALLSTACK_UP_ID = 'workbench.action.debug.callStackUp'; +export const CALLSTACK_DOWN_ID = 'workbench.action.debug.callStackDown'; export const DEBUG_COMMAND_CATEGORY = 'Debug'; export const RESTART_LABEL = { value: nls.localize('restartDebug', "Restart"), original: 'Restart' }; @@ -84,6 +88,10 @@ export const DEBUG_RUN_LABEL = { value: nls.localize('startWithoutDebugging', "S export const NEXT_DEBUG_CONSOLE_LABEL = { value: nls.localize('nextDebugConsole', "Focus Next Debug Console"), original: 'Focus Next Debug Console' }; export const PREV_DEBUG_CONSOLE_LABEL = { value: nls.localize('prevDebugConsole', "Focus Previous Debug Console"), original: 'Focus Previous Debug Console' }; export const OPEN_LOADED_SCRIPTS_LABEL = { value: nls.localize('openLoadedScript', "Open Loaded Script..."), original: 'Open Loaded Script...' }; +export const CALLSTACK_TOP_LABEL = { value: nls.localize('callStackTop', "Navigate to Top of Call Stack"), original: 'Navigate to Top of Call Stack' }; +export const CALLSTACK_BOTTOM_LABEL = { value: nls.localize('callStackBottom', "Navigate to Bottom of Call Stack"), original: 'Navigate to Bottom of Call Stack' }; +export const CALLSTACK_UP_LABEL = { value: nls.localize('callStackUp', "Navigate Up Call Stack"), original: 'Navigate Up Call Stack' }; +export const CALLSTACK_DOWN_LABEL = { value: nls.localize('callStackDown', "Navigate Down Call Stack"), original: 'Navigate Down Call Stack' }; export const SELECT_DEBUG_CONSOLE_LABEL = { value: nls.localize('selectDebugConsole', "Select Debug Console"), original: 'Select Debug Console' }; @@ -180,6 +188,103 @@ async function changeDebugConsoleFocus(accessor: ServicesAccessor, next: boolean } } +async function navigateCallStack(debugService: IDebugService, down: boolean) { + const frame = debugService.getViewModel().focusedStackFrame; + if (frame) { + + let callStack = frame.thread.getCallStack(); + let index = callStack.findIndex(elem => elem.frameId === frame.frameId); + let nextVisibleFrame; + if (down) { + if (index >= callStack.length - 1) { + if ((frame.thread).reachedEndOfCallStack) { + goToTopOfCallStack(debugService); + return; + } else { + await debugService.getModel().fetchCallstack(frame.thread, 20); + callStack = frame.thread.getCallStack(); + index = callStack.findIndex(elem => elem.frameId === frame.frameId); + } + } + nextVisibleFrame = findNextVisibleFrame(true, callStack, index); + } else { + if (index <= 0) { + goToBottomOfCallStack(debugService); + return; + } + nextVisibleFrame = findNextVisibleFrame(false, callStack, index); + } + + if (nextVisibleFrame) { + debugService.focusStackFrame(nextVisibleFrame); + } + } +} + +async function goToBottomOfCallStack(debugService: IDebugService) { + const thread = debugService.getViewModel().focusedThread; + if (thread) { + await debugService.getModel().fetchCallstack(thread); + const callStack = thread.getCallStack(); + if (callStack.length > 0) { + const nextVisibleFrame = findNextVisibleFrame(false, callStack, 0); // must consider the next frame up first, which will be the last frame + if (nextVisibleFrame) { + debugService.focusStackFrame(nextVisibleFrame); + } + } + } +} + +function goToTopOfCallStack(debugService: IDebugService) { + const thread = debugService.getViewModel().focusedThread; + + if (thread) { + debugService.focusStackFrame(thread.getTopStackFrame()); + } +} + +/** + * Finds next frame that is not skipped by SkipFiles. Skips frame at index and starts searching at next. + * Must satisfy `0 <= startIndex <= callStack - 1` + * @param down specifies whether to search downwards if the current file is skipped. + * @param callStack the call stack to search + * @param startIndex the index to start the search at + */ +function findNextVisibleFrame(down: boolean, callStack: readonly IStackFrame[], startIndex: number) { + + if (startIndex >= callStack.length) { + startIndex = callStack.length - 1; + } else if (startIndex < 0) { + startIndex = 0; + } + + let index = startIndex; + + let currFrame; + do { + if (down) { + if (index === callStack.length - 1) { + index = 0; + } else { + index++; + } + } else { + if (index === 0) { + index = callStack.length - 1; + } else { + index--; + } + } + + currFrame = callStack[index]; + if (!(currFrame.source.presentationHint === 'deemphasize' || currFrame.presentationHint === 'deemphasize')) { + return currFrame; + } + } while (index !== startIndex); // end loop when we've just checked the start index, since that should be the last one checked + + return undefined; +} + // These commands are used in call stack context menu, call stack inline actions, command palette, debug toolbar, mac native touch bar // When the command is exectued in the context of a thread(context menu on a thread, inline call stack action) we pass the thread id // Otherwise when it is executed "globaly"(using the touch bar, debug toolbar, command palette) we do not pass any id and just take whatever is the focussed thread @@ -261,6 +366,39 @@ CommandsRegistry.registerCommand({ } }); + +CommandsRegistry.registerCommand({ + id: CALLSTACK_TOP_ID, + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + const debugService = accessor.get(IDebugService); + goToTopOfCallStack(debugService); + } +}); + +CommandsRegistry.registerCommand({ + id: CALLSTACK_BOTTOM_ID, + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + const debugService = accessor.get(IDebugService); + await goToBottomOfCallStack(debugService); + } +}); + +CommandsRegistry.registerCommand({ + id: CALLSTACK_UP_ID, + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + const debugService = accessor.get(IDebugService); + navigateCallStack(debugService, false); + } +}); + +CommandsRegistry.registerCommand({ + id: CALLSTACK_DOWN_ID, + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + const debugService = accessor.get(IDebugService); + navigateCallStack(debugService, true); + } +}); + MenuRegistry.appendMenuItem(MenuId.EditorContext, { command: { id: JUMP_TO_CURSOR_ID, diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 4a53a4e0c93..de26f713ef0 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -955,7 +955,7 @@ export class DebugSession implements IDebugSession { if (thread) { // Call fetch call stack twice, the first only return the top stack frame. // Second retrieves the rest of the call stack. For performance reasons #25605 - const promises = this.model.fetchCallStack(thread); + const promises = this.model.refreshTopOfCallstack(thread); const focus = async () => { if (focusedThreadDoesNotExist || (!event.body.preserveFocusHint && thread.getCallStack().length)) { const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index baac9206484..a8397f69aef 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -602,6 +602,8 @@ export interface IDebugModel extends ITreeElement { onDidChangeBreakpoints: Event; onDidChangeCallStack: Event; onDidChangeWatchExpressions: Event; + + fetchCallstack(thread: IThread, levels?: number): Promise; } /** diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 4b20e069cd2..1f50eb36368 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -1244,7 +1244,31 @@ export class DebugModel implements IDebugModel { } } - fetchCallStack(thread: Thread): { topCallStack: Promise; wholeCallStack: Promise } { + /** + * Update the call stack and notify the call stack view that changes have occurred. + */ + async fetchCallstack(thread: IThread, levels?: number): Promise { + + if ((thread).reachedEndOfCallStack) { + return; + } + + const totalFrames = thread.stoppedDetails?.totalFrames; + const remainingFrames = (typeof totalFrames === 'number') ? (totalFrames - thread.getCallStack().length) : undefined; + + if (!levels || (remainingFrames && levels > remainingFrames)) { + levels = remainingFrames; + } + + if (levels && levels > 0) { + await (thread).fetchCallStack(levels); + this._onDidChangeCallStack.fire(); + } + + return; + } + + refreshTopOfCallstack(thread: Thread): { topCallStack: Promise; wholeCallStack: Promise } { if (thread.session.capabilities.supportsDelayedStackTraceLoading) { // For improved performance load the first stack frame and then load the rest async. let topCallStack = Promise.resolve(); -- cgit v1.2.3 From a6238ac59c487435fb5810986771999f1d6ea8bb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 15:15:03 -0700 Subject: Add icon in defaultIcon description --- src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index c1f9c0af306..acdd4ce698d 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -58,6 +58,7 @@ const terminalConfiguration: IConfigurationNode = { description: localize('terminal.integrated.tabs.defaultIcon', "Controls the terminal tab's default icon."), type: 'string', enum: Codicon.getAll().map(icon => icon.id), + markdownEnumDescriptions: Array.from(Codicon.getAll(), icon => `$(${icon.id})`), default: undefined, }, [TerminalSettingId.TabsEnabled]: { -- cgit v1.2.3 From 510656a44a5dfa3ad63190f0641018d858e222a9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:02:50 -0700 Subject: Replace all Terminal codicon references with default icon --- .../contrib/terminal/browser/terminalActions.ts | 3 ++- .../contrib/terminal/browser/terminalIcon.ts | 7 ++++--- .../contrib/terminal/browser/terminalInstance.ts | 5 ++++- .../terminal/browser/terminalProfileQuickpick.ts | 5 +++-- .../browser/terminalProfileResolverService.ts | 17 +++++++++++---- .../terminal/browser/terminalQuickAccess.ts | 6 ++++-- .../contrib/terminal/browser/terminalTabsList.ts | 2 +- .../contrib/terminal/browser/terminalView.ts | 10 +++++---- .../workbench/contrib/terminal/common/terminal.ts | 2 ++ .../terminal/common/terminalConfiguration.ts | 24 ++++++---------------- 10 files changed, 45 insertions(+), 36 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 0c70784c0e3..540f153ea32 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -1709,6 +1709,7 @@ export function registerTerminalActions() { const themeService = accessor.get(IThemeService); const groupService = accessor.get(ITerminalGroupService); const notificationService = accessor.get(INotificationService); + const picks: ITerminalQuickPickItem[] = []; if (groupService.instances.length <= 1) { notificationService.warn(localize('workbench.action.terminal.join.insufficientTerminals', 'Insufficient terminals for the join action')); @@ -1718,7 +1719,7 @@ export function registerTerminalActions() { for (const terminal of otherInstances) { const group = groupService.getGroupForInstance(terminal); if (group?.terminalInstances.length === 1) { - const iconId = getIconId(terminal); + const iconId = getIconId(accessor, terminal); const label = `$(${iconId}): ${terminal.title}`; const iconClasses: string[] = []; const colorClass = getColorClass(terminal); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts index b098cc3ece1..89acbedfa80 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Codicon } from 'vs/base/common/codicons'; import { hash } from 'vs/base/common/hash'; import { URI } from 'vs/base/common/uri'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionTerminalProfile, ITerminalProfile } from 'vs/platform/terminal/common/terminal'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal'; import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; @@ -116,9 +117,9 @@ export function getUriClasses(terminal: ITerminalInstance | IExtensionTerminalPr return iconClasses; } -export function getIconId(terminal: ITerminalInstance | IExtensionTerminalProfile | ITerminalProfile): string { +export function getIconId(accessor: ServicesAccessor, terminal: ITerminalInstance | IExtensionTerminalProfile | ITerminalProfile): string { if (!terminal.icon || (terminal.icon instanceof Object && !('id' in terminal.icon))) { - return Codicon.terminal.id; + return accessor.get(ITerminalProfileResolverService).getDefaultIcon().id; } return typeof terminal.icon === 'string' ? terminal.icon : terminal.icon.id; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index edf4d2b4198..a02e18413dc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -86,6 +86,7 @@ import type { IMarker, ITerminalAddon, Terminal as XTermTerminal } from 'xterm'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; const enum Constants { /** @@ -548,7 +549,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _getIcon(): TerminalIcon | undefined { if (!this._icon) { - this._icon = this._processManager.processState >= ProcessState.Launching ? Codicon.terminal : undefined; + this._icon = this._processManager.processState >= ProcessState.Launching + ? getIconRegistry().getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)) + : undefined; } return this._icon; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts index e715b730b66..e67654f2c70 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts @@ -11,7 +11,7 @@ import { getUriClasses, getColorClass, getColorStyleElement } from 'vs/workbench import { configureTerminalProfileIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons'; import * as nls from 'vs/nls'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; import { IQuickPickTerminalObject, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; @@ -22,6 +22,7 @@ type DefaultProfileName = string; export class TerminalProfileQuickpick { constructor( @ITerminalProfileService private readonly _terminalProfileService: ITerminalProfileService, + @ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IThemeService private readonly _themeService: IThemeService @@ -155,7 +156,7 @@ export class TerminalProfileQuickpick { } } if (!icon || !getIconRegistry().getIcon(icon.id)) { - icon = Codicon.terminal; + icon = this._terminalProfileResolverService.getDefaultIcon(); } const uriClasses = getUriClasses(contributed, this._themeService.getColorTheme().type, true); const colorClass = getColorClass(contributed); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts index 1554d580f33..2abd550fedc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts @@ -97,11 +97,11 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro resolveIcon(shellLaunchConfig: IShellLaunchConfig, os: OperatingSystem): void { if (shellLaunchConfig.icon) { - shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || Codicon.terminal; + shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || this.getDefaultIcon(); return; } if (shellLaunchConfig.customPtyImplementation) { - shellLaunchConfig.icon = Codicon.terminal; + shellLaunchConfig.icon = this.getDefaultIcon(); return; } if (shellLaunchConfig.executable) { @@ -111,6 +111,13 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro if (defaultProfile) { shellLaunchConfig.icon = defaultProfile.icon; } + if (!shellLaunchConfig.icon) { + shellLaunchConfig.icon = this.getDefaultIcon(); + } + } + + getDefaultIcon(): TerminalIcon & ThemeIcon { + return this._iconRegistry.getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)) || Codicon.terminal; } async resolveShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig, options: IShellLaunchConfigResolveOptions): Promise { @@ -138,10 +145,11 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro // Verify the icon is valid, and fallback correctly to the generic terminal id if there is // an issue + console.log('icon', this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)); shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || this._getCustomIcon(resolvedProfile.icon) - || this._iconRegistry.getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)) - || Codicon.terminal; + || this.getDefaultIcon(); + console.log('icon2', shellLaunchConfig.icon); // Override the name if specified if (resolvedProfile.overrideName) { @@ -240,6 +248,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro if (defaultProfileName && typeof defaultProfileName === 'string') { return this._terminalProfileService.availableProfiles.find(e => e.profileName === defaultProfileName); } + return undefined; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts index af7953a3268..71bdeeb5de4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts @@ -16,6 +16,7 @@ import { getColorClass, getIconId, getUriClasses } from 'vs/workbench/contrib/te import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings'; import { TerminalLocation } from 'vs/platform/terminal/common/terminal'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; let terminalPicks: Array = []; export class TerminalQuickAccessProvider extends PickerQuickAccessProvider { @@ -27,7 +28,8 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider 1 ? `${groupInfo.groupIndex + 1}.${terminalIndex + 1}` diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index d9fc17533ed..14890b1b9bf 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -309,7 +309,7 @@ class TerminalTabsRenderer implements IListRenderer; getDefaultShell(options: IShellLaunchConfigResolveOptions): Promise; getDefaultShellArgs(options: IShellLaunchConfigResolveOptions): Promise; + getDefaultIcon(): TerminalIcon & ThemeIcon; getEnvironment(remoteAuthority: string | undefined): Promise; createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise; } diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index acdd4ce698d..5c982b8dc56 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -10,6 +10,7 @@ import { TerminalLocationString, TerminalSettingId } from 'vs/platform/terminal/ import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { Registry } from 'vs/platform/registry/common/platform'; import { Codicon } from 'vs/base/common/codicons'; +import { terminalColorSchema, terminalIconSchema } from 'vs/platform/terminal/common/terminalPlatformConfiguration'; const terminalDescriptors = '\n- ' + [ '`\${cwd}`: ' + localize("cwd", "the terminal's current working directory"), @@ -40,26 +41,13 @@ const terminalConfiguration: IConfigurationNode = { default: false }, [TerminalSettingId.TabsDefaultColor]: { - description: localize('terminal.integrated.tabs.defaultColor', "Controls the terminal tab icon's default color."), - type: 'string', - enum: [ - 'terminal.ansiBlack', - 'terminal.ansiRed', - 'terminal.ansiGreen', - 'terminal.ansiYellow', - 'terminal.ansiBlue', - 'terminal.ansiMagenta', - 'terminal.ansiCyan', - 'terminal.ansiWhite' - ], - default: undefined, + description: localize('terminal.integrated.tabs.defaultColor', "A theme color ID to associate with terminals by default."), + ...terminalColorSchema }, [TerminalSettingId.TabsDefaultIcon]: { - description: localize('terminal.integrated.tabs.defaultIcon', "Controls the terminal tab's default icon."), - type: 'string', - enum: Codicon.getAll().map(icon => icon.id), - markdownEnumDescriptions: Array.from(Codicon.getAll(), icon => `$(${icon.id})`), - default: undefined, + description: localize('terminal.integrated.tabs.defaultIcon', "A codicon ID to associate with terminals by default."), + ...terminalIconSchema, + default: Codicon.terminal.id, }, [TerminalSettingId.TabsEnabled]: { description: localize('terminal.integrated.tabs.enabled', 'Controls whether terminal tabs display as a list to the side of the terminal. When this is disabled a dropdown will display instead.'), -- cgit v1.2.3 From 22f3f6064e9fc2cf8247932436e7f910641370af Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:29:08 -0700 Subject: Move some text out of localization string for integrated terminal settings (#154318) Fixes #154317 --- .../terminal/common/terminalConfiguration.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 83a67239048..95fb4b4e3e6 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -34,7 +34,7 @@ const terminalConfiguration: IConfigurationNode = { type: 'object', properties: { [TerminalSettingId.SendKeybindingsToShell]: { - markdownDescription: localize('terminal.integrated.sendKeybindingsToShell', "Dispatches most keybindings to the terminal instead of the workbench, overriding `#terminal.integrated.commandsToSkipShell#`, which can be used alternatively for fine tuning."), + markdownDescription: localize('terminal.integrated.sendKeybindingsToShell', "Dispatches most keybindings to the terminal instead of the workbench, overriding {0}, which can be used alternatively for fine tuning.", '`#terminal.integrated.commandsToSkipShell#`'), type: 'boolean', default: false }, @@ -106,17 +106,17 @@ const terminalConfiguration: IConfigurationNode = { [TerminalSettingId.ShellIntegrationDecorationIconSuccess]: { type: 'string', default: 'primitive-dot', - markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconSuccess', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to `''` to hide the icon or disable decorations with `#terminal.integrated.shellIntegration.decorationsEnabled#`") + markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconSuccess', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`') }, [TerminalSettingId.ShellIntegrationDecorationIconError]: { type: 'string', default: 'error-small', - markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code. Set to `''` to hide the icon or disable decorations with `#terminal.integrated.shellIntegration.decorationsEnabled#`.") + markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`') }, [TerminalSettingId.ShellIntegrationDecorationIcon]: { type: 'string', default: 'circle-outline', - markdownDescription: localize('terminal.integrated.shellIntegration.decorationIcon', "Controls the icon that will be used for skipped/empty commands. Set to `''` to hide the icon or disable decorations with `#terminal.integrated.shellIntegration.decorationsEnabled#`") + markdownDescription: localize('terminal.integrated.shellIntegration.decorationIcon', "Controls the icon that will be used for skipped/empty commands. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`') }, [TerminalSettingId.TabsFocusMode]: { type: 'string', @@ -139,7 +139,7 @@ const terminalConfiguration: IConfigurationNode = { default: false }, [TerminalSettingId.AltClickMovesCursor]: { - markdownDescription: localize('terminal.integrated.altClickMovesCursor', "If enabled, alt/option + click will reposition the prompt cursor to underneath the mouse when `#editor.multiCursorModifier#` is set to `'alt'` (the default value). This may not work reliably depending on your shell."), + markdownDescription: localize('terminal.integrated.altClickMovesCursor', "If enabled, alt/option + click will reposition the prompt cursor to underneath the mouse when {0} is set to {1} (the default value). This may not work reliably depending on your shell.", '`#editor.multiCursorModifier#`', '`\'alt\'`'), type: 'boolean', default: true }, @@ -159,7 +159,7 @@ const terminalConfiguration: IConfigurationNode = { default: true }, [TerminalSettingId.FontFamily]: { - markdownDescription: localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to `#editor.fontFamily#`'s value."), + markdownDescription: localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to {0}'s value.", '`#editor.fontFamily#`'), type: 'string' }, // TODO: Support font ligatures @@ -254,7 +254,7 @@ const terminalConfiguration: IConfigurationNode = { default: TerminalCursorStyle.BLOCK }, [TerminalSettingId.CursorWidth]: { - markdownDescription: localize('terminal.integrated.cursorWidth', "Controls the width of the cursor when `#terminal.integrated.cursorStyle#` is set to `line`."), + markdownDescription: localize('terminal.integrated.cursorWidth', "Controls the width of the cursor when {0} is set to {1}.", '`#terminal.integrated.cursorStyle#`', '`line`'), type: 'number', default: 1 }, @@ -354,7 +354,8 @@ const terminalConfiguration: IConfigurationNode = { 'terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell but instead always be handled by VS Code. This allows keybindings that would normally be consumed by the shell to act instead the same as when the terminal is not focused, for example `Ctrl+P` to launch Quick Open.\n\n \n\nMany commands are skipped by default. To override a default and pass that command's keybinding to the shell instead, add the command prefixed with the `-` character. For example add `-workbench.action.quickOpen` to allow `Ctrl+P` to reach the shell.\n\n \n\nThe following list of default skipped commands is truncated when viewed in Settings Editor. To see the full list, {1} and search for the first command from the list below.\n\n \n\nDefault Skipped Commands:\n\n{0}", DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n'), - `[${localize('openDefaultSettingsJson', "open the default settings JSON")}](command:workbench.action.openRawDefaultSettings '${localize('openDefaultSettingsJson.capitalized', "Open Default Settings (JSON)")}')` + `[${localize('openDefaultSettingsJson', "open the default settings JSON")}](command:workbench.action.openRawDefaultSettings '${localize('openDefaultSettingsJson.capitalized', "Open Default Settings (JSON)")}')`, + ), type: 'array', items: { @@ -363,7 +364,7 @@ const terminalConfiguration: IConfigurationNode = { default: [] }, [TerminalSettingId.AllowChords]: { - markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass `#terminal.integrated.commandsToSkipShell#`, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code)."), + markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass {0}, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code).", '`#terminal.integrated.commandsToSkipShell#`'), type: 'boolean', default: true }, @@ -464,7 +465,7 @@ const terminalConfiguration: IConfigurationNode = { default: 30, }, [TerminalSettingId.LocalEchoEnabled]: { - markdownDescription: localize('terminal.integrated.localEchoEnabled', "When local echo should be enabled. This will override `#terminal.integrated.localEchoLatencyThreshold#`"), + markdownDescription: localize('terminal.integrated.localEchoEnabled', "When local echo should be enabled. This will override {0}", '`#terminal.integrated.localEchoLatencyThreshold#`'), type: 'string', enum: ['on', 'off', 'auto'], enumDescriptions: [ -- cgit v1.2.3 From c4b08408c8e9ed572e29e1d106c8d9d105716e41 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:30:46 -0700 Subject: Fix compile --- src/vs/workbench/contrib/terminal/browser/terminalView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index ed41d6eac2a..f3f77734984 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -375,7 +375,7 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @ICommandService private readonly _commandService: ICommandService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(new MenuItemAction( -- cgit v1.2.3 From 2a5d381d162212f47c495eacf2e84a372a74f5ef Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 6 Jul 2022 20:15:58 -0400 Subject: only show output button when a command's markers haven't been disposed of (#154220) * fix #154215 * make hasOutput a function * fix test * Update src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts * actually fix tests --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- .../workbench/contrib/terminal/browser/xterm/decorationAddon.ts | 2 +- src/vs/workbench/contrib/terminal/common/terminal.ts | 2 +- .../terminal/test/browser/links/terminalLinkOpeners.test.ts | 6 +++--- .../contrib/terminal/test/browser/xterm/decorationAddon.test.ts | 8 ++++---- 5 files changed, 10 insertions(+), 10 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index edf4d2b4198..6c3e3c9f25c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -877,7 +877,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { description, id: entry.timestamp.toString(), command: entry, - buttons: entry.hasOutput ? buttons : undefined + buttons: entry.hasOutput() ? buttons : undefined }); commandMap.add(label); } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index e7e3137ae30..45b820a935c 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -365,7 +365,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { run: () => this._clipboardService.writeText(command.command) }); } - if (command.hasOutput) { + if (command.hasOutput()) { if (actions.length > 0) { actions.push(new Separator()); } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 8346edc1b8e..109988ca671 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -341,7 +341,7 @@ export interface ITerminalCommand { cwd?: string; exitCode?: number; marker?: IXtermMarker; - hasOutput: boolean; + hasOutput(): boolean; getOutput(): string | undefined; genericMarkProperties?: IGenericMarkProperties; } diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts index e7eea1e62fc..225dd72bbdc 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts @@ -110,7 +110,7 @@ suite('Workbench - TerminalLinkOpeners', () => { marker: { line: 0 } as Partial as any, - hasOutput: true + hasOutput() { return true; } }]); fileService.setFiles([ URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo/bar.txt' }), @@ -188,7 +188,7 @@ suite('Workbench - TerminalLinkOpeners', () => { marker: { line: 0 } as Partial as any, - hasOutput: true + hasOutput() { return true; } }]); await opener.open({ text: 'file.txt', @@ -237,7 +237,7 @@ suite('Workbench - TerminalLinkOpeners', () => { marker: { line: 0 } as Partial as any, - hasOutput: true + hasOutput() { return true; } }]); await opener.open({ text: 'file.txt', diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts index 80802218586..3ea312e1e1e 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts @@ -56,21 +56,21 @@ suite('DecorationAddon', () => { suite('registerDecoration', async () => { test('should throw when command has no marker', async () => { - throws(() => decorationAddon.registerCommandDecoration({ command: 'cd src', timestamp: Date.now(), hasOutput: false } as ITerminalCommand)); + throws(() => decorationAddon.registerCommandDecoration({ command: 'cd src', timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand)); }); test('should return undefined when marker has been disposed of', async () => { const marker = xterm.registerMarker(1); marker?.dispose(); - strictEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: false } as ITerminalCommand), undefined); + strictEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand), undefined); }); test('should return undefined when command is just empty chars', async () => { const marker = xterm.registerMarker(1); marker?.dispose(); - strictEqual(decorationAddon.registerCommandDecoration({ command: ' ', marker, timestamp: Date.now(), hasOutput: false } as ITerminalCommand), undefined); + strictEqual(decorationAddon.registerCommandDecoration({ command: ' ', marker, timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand), undefined); }); test('should return decoration when marker has not been disposed of', async () => { const marker = xterm.registerMarker(2); - notEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: false } as ITerminalCommand), undefined); + notEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand), undefined); }); }); }); -- cgit v1.2.3 From d5d6e089978695d51760b35a64510d8d0e999534 Mon Sep 17 00:00:00 2001 From: MonadChains Date: Thu, 7 Jul 2022 02:16:10 +0200 Subject: Add command to copy output of the last command (#152097) (#153235) * Add command to copy output of the last command (#152097) * Polish changes, casing, wording, etc. Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/vs/workbench/contrib/terminal/browser/terminal.ts | 6 ++++++ .../workbench/contrib/terminal/browser/terminalActions.ts | 14 ++++++++++++++ .../contrib/terminal/browser/terminalInstance.ts | 15 +++++++++++++++ src/vs/workbench/contrib/terminal/common/terminal.ts | 2 ++ 4 files changed, 37 insertions(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 3b0b1e1e250..8f2474b5dcd 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -678,6 +678,12 @@ export interface ITerminalInstance { */ copySelection(asHtml?: boolean, command?: ITerminalCommand): Promise; + + /** + * Copies the ouput of the last command + */ + copyLastCommandOutput(): Promise; + /** * Current selection in the terminal. */ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 0c70784c0e3..2f7da096e88 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -327,6 +327,20 @@ export function registerTerminalActions() { } } }); + registerAction2(class extends Action2 { + constructor() { + super({ + id: TerminalCommandId.CopyLastCommand, + title: { value: localize('workbench.action.terminal.copyLastCommand', 'Copy Last Command'), original: 'Copy Last Command' }, + f1: true, + category, + precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated) + }); + } + async run(accessor: ServicesAccessor): Promise { + await accessor.get(ITerminalService).activeInstance?.copyLastCommandOutput(); + } + }); registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 6c3e3c9f25c..995d7d64cd8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1239,6 +1239,21 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } + async copyLastCommandOutput(): Promise { + const commands = this.capabilities.get(TerminalCapability.CommandDetection)?.commands; + if (!commands || commands.length === 0) { + return; + } + const command = commands[commands.length - 1]; + if (!command?.hasOutput) { + return; + } + const output = command.getOutput(); + if (output) { + await this._clipboardService.writeText(output); + } + } + get selection(): string | undefined { return this.xterm && this.hasSelection() ? this.xterm.raw.getSelection() : undefined; } diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index 109988ca671..52e3969a13a 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -476,6 +476,7 @@ export const enum TerminalCommandId { OpenFileLink = 'workbench.action.terminal.openFileLink', OpenWebLink = 'workbench.action.terminal.openUrlLink', RunRecentCommand = 'workbench.action.terminal.runRecentCommand', + CopyLastCommand = 'workbench.action.terminal.copyLastCommand', GoToRecentDirectory = 'workbench.action.terminal.goToRecentDirectory', CopySelection = 'workbench.action.terminal.copySelection', CopySelectionAsHtml = 'workbench.action.terminal.copySelectionAsHtml', @@ -574,6 +575,7 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ TerminalCommandId.Clear, TerminalCommandId.CopySelection, TerminalCommandId.CopySelectionAsHtml, + TerminalCommandId.CopyLastCommand, TerminalCommandId.DeleteToLineStart, TerminalCommandId.DeleteWordLeft, TerminalCommandId.DeleteWordRight, -- cgit v1.2.3 From a3153bb9dc4816c4f4034686997a6232b923769f Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 6 Jul 2022 20:25:03 -0400 Subject: Update src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts --- .../workbench/contrib/terminal/browser/terminalProfileResolverService.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts index 2abd550fedc..33e242184de 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts @@ -145,7 +145,6 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro // Verify the icon is valid, and fallback correctly to the generic terminal id if there is // an issue - console.log('icon', this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)); shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || this._getCustomIcon(resolvedProfile.icon) || this.getDefaultIcon(); -- cgit v1.2.3 From d39ef2fc829dffe33ff7b9c341d787bb1198ef63 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 6 Jul 2022 20:25:24 -0400 Subject: Update src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts --- .../workbench/contrib/terminal/browser/terminalProfileResolverService.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts index 33e242184de..f920f87442f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts @@ -148,7 +148,6 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || this._getCustomIcon(resolvedProfile.icon) || this.getDefaultIcon(); - console.log('icon2', shellLaunchConfig.icon); // Override the name if specified if (resolvedProfile.overrideName) { -- cgit v1.2.3 From 2d9947ba4e53c0889c07e3a415572ec8bad1529e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 6 Jul 2022 20:47:00 -0400 Subject: more specific setting names --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- .../contrib/terminal/browser/terminalProfileResolverService.ts | 4 ++-- src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index a02e18413dc..b392eb5bd70 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -550,7 +550,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _getIcon(): TerminalIcon | undefined { if (!this._icon) { this._icon = this._processManager.processState >= ProcessState.Launching - ? getIconRegistry().getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)) + ? getIconRegistry().getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIconId)) : undefined; } return this._icon; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts index f920f87442f..b133c8e5d8d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts @@ -117,7 +117,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro } getDefaultIcon(): TerminalIcon & ThemeIcon { - return this._iconRegistry.getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)) || Codicon.terminal; + return this._iconRegistry.getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIconId)) || Codicon.terminal; } async resolveShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig, options: IShellLaunchConfigResolveOptions): Promise { @@ -157,7 +157,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro // Apply the color shellLaunchConfig.color = shellLaunchConfig.color || resolvedProfile.color - || this._configurationService.getValue(TerminalSettingId.TabsDefaultColor); + || this._configurationService.getValue(TerminalSettingId.TabsDefaultIconColor); // Resolve useShellEnvironment based on the setting if it's not set if (shellLaunchConfig.useShellEnvironment === undefined) { diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 5c982b8dc56..0458ff05172 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -40,12 +40,12 @@ const terminalConfiguration: IConfigurationNode = { type: 'boolean', default: false }, - [TerminalSettingId.TabsDefaultColor]: { - description: localize('terminal.integrated.tabs.defaultColor', "A theme color ID to associate with terminals by default."), + [TerminalSettingId.TabsDefaultIconColor]: { + description: localize('terminal.integrated.tabs.defaultIconColor', "A theme color ID to associate with terminals by default."), ...terminalColorSchema }, - [TerminalSettingId.TabsDefaultIcon]: { - description: localize('terminal.integrated.tabs.defaultIcon', "A codicon ID to associate with terminals by default."), + [TerminalSettingId.TabsDefaultIconId]: { + description: localize('terminal.integrated.tabs.defaultIconId', "A codicon ID to associate with terminals by default."), ...terminalIconSchema, default: Codicon.terminal.id, }, -- cgit v1.2.3 From 91b82c0f0b9d0c9a9135252663522a563be3f6eb Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 6 Jul 2022 21:59:27 -0400 Subject: increase barrier for available profiles to be ready (#154290) * fix #138999 Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts index 60c49b4b34b..7bdc8975d82 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts @@ -67,7 +67,7 @@ export class TerminalProfileService implements ITerminalProfileService { // Wait up to 5 seconds for profiles to be ready so it's assured that we know the actual // default terminal before launching the first terminal. This isn't expected to ever take // this long. - this._profilesReadyBarrier = new AutoOpenBarrier(5000); + this._profilesReadyBarrier = new AutoOpenBarrier(20000); this.refreshAvailableProfiles(); this._setupConfigListener(); } -- cgit v1.2.3 From 374066b82984c3bf8afaa4828147ec29bd883bc8 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 6 Jul 2022 19:06:36 -0700 Subject: re #153743. Move codicon out of translation string (#154323) --- .../notebook/browser/contrib/editorStatusBar/editorStatusBar.ts | 2 +- .../contrib/notebook/browser/controller/insertCellActions.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 285dae8f94e..53ce0466795 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -243,7 +243,7 @@ registerAction2(class extends Action2 { quickPickItems.push({ id: 'installSuggested', description: suggestedExtension.displayName ?? suggestedExtension.extensionId, - label: nls.localize('installSuggestedKernel', '$({0}) Install suggested extensions', Codicon.lightbulb.id), + label: `$(${Codicon.lightbulb.id}) ` + nls.localize('installSuggestedKernel', 'Install suggested extensions'), }); } // there is no kernel, show the install from marketplace diff --git a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts index 6a85c41a24a..454b565d71e 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts @@ -224,7 +224,7 @@ registerAction2(class InsertMarkdownCellAtTopAction extends NotebookAction { MenuRegistry.appendMenuItem(MenuId.NotebookCellBetween, { command: { id: INSERT_CODE_CELL_BELOW_COMMAND_ID, - title: localize('notebookActions.menu.insertCode', "$(add) Code"), + title: '$(add) ' + localize('notebookActions.menu.insertCode', "Code"), tooltip: localize('notebookActions.menu.insertCode.tooltip', "Add Code Cell") }, order: 0, @@ -269,7 +269,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, { MenuRegistry.appendMenuItem(MenuId.NotebookCellListTop, { command: { id: INSERT_CODE_CELL_AT_TOP_COMMAND_ID, - title: localize('notebookActions.menu.insertCode', "$(add) Code"), + title: '$(add) ' + localize('notebookActions.menu.insertCode', "Code"), tooltip: localize('notebookActions.menu.insertCode.tooltip', "Add Code Cell") }, order: 0, @@ -299,7 +299,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookCellListTop, { MenuRegistry.appendMenuItem(MenuId.NotebookCellBetween, { command: { id: INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID, - title: localize('notebookActions.menu.insertMarkdown', "$(add) Markdown"), + title: '$(add) ' + localize('notebookActions.menu.insertMarkdown', "Markdown"), tooltip: localize('notebookActions.menu.insertMarkdown.tooltip', "Add Markdown Cell") }, order: 1, @@ -331,7 +331,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, { MenuRegistry.appendMenuItem(MenuId.NotebookCellListTop, { command: { id: INSERT_MARKDOWN_CELL_AT_TOP_COMMAND_ID, - title: localize('notebookActions.menu.insertMarkdown', "$(add) Markdown"), + title: '$(add) ' + localize('notebookActions.menu.insertMarkdown', "Markdown"), tooltip: localize('notebookActions.menu.insertMarkdown.tooltip', "Add Markdown Cell") }, order: 1, -- cgit v1.2.3 From ffdb7543feb9abccee23d2b3064bb5bd3d8593a7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 6 Jul 2022 19:13:51 -0700 Subject: Correct conditional calling func instead of comparing Part of #152097 --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 995d7d64cd8..370ed54dd2d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1245,7 +1245,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } const command = commands[commands.length - 1]; - if (!command?.hasOutput) { + if (!command?.hasOutput()) { return; } const output = command.getOutput(); -- cgit v1.2.3 From 93d1f7c88157cc4594ff7154b0f2ad6efc4db08c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 6 Jul 2022 22:17:44 -0400 Subject: use user's `.zsh_history` (#154300) --- .../workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh index a94e7c11c71..595c1261e18 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh @@ -21,6 +21,10 @@ if [[ "$VSCODE_INJECTION" == "1" ]]; then . $USER_ZDOTDIR/.zshrc ZDOTDIR=$VSCODE_ZDOTDIR fi + + if [[ -f $USER_ZDOTDIR/.zsh_history ]]; then + HISTFILE=$USER_ZDOTDIR/.zsh_history + fi fi # Shell integration was disabled by the shell, exit without warning assuming either the shell has -- cgit v1.2.3 From d6114a70bea1c9163c7c3538e31243affd3b9fd4 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 6 Jul 2022 22:23:30 -0400 Subject: Remove app insights (#154296) * Remove app insights * Update product service to remove asimovKey --- src/vs/workbench/contrib/debug/node/telemetryApp.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/debug/node/telemetryApp.ts b/src/vs/workbench/contrib/debug/node/telemetryApp.ts index ab6d37993ca..601ff4f9b1e 100644 --- a/src/vs/workbench/contrib/debug/node/telemetryApp.ts +++ b/src/vs/workbench/contrib/debug/node/telemetryApp.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { Server } from 'vs/base/parts/ipc/node/ipc.cp'; -import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc'; +import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; -const appender = new AppInsightsAppender(process.argv[2], JSON.parse(process.argv[3]), process.argv[4]); +const appender = new OneDataSystemAppender(undefined, process.argv[2], JSON.parse(process.argv[3]), process.argv[4]); process.once('exit', () => appender.flush()); const channel = new TelemetryAppenderChannel([appender]); -- cgit v1.2.3 From e337c0289b320ad9e2e6d5b9b63958f75fdaaa7d Mon Sep 17 00:00:00 2001 From: Robert Jin <20613660+jzyrobert@users.noreply.github.com> Date: Thu, 7 Jul 2022 07:55:48 +0100 Subject: Edit showFoldingControls to have a never setting (#153764) --- .../workbench/contrib/comments/browser/commentsEditorContribution.ts | 2 +- src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts | 3 ++- src/vs/workbench/contrib/notebook/common/notebookOptions.ts | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index a0e155d0bdd..335976fbac8 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -859,7 +859,7 @@ export class CommentController implements IEditorContribution { } const options = this.editor.getOptions(); - if (options.get(EditorOption.folding)) { + if (options.get(EditorOption.folding) && options.get(EditorOption.showFoldingControls) !== 'never') { lineDecorationsWidth -= 16; } lineDecorationsWidth += 9; diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 466fd1bf565..6111d9f1caa 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -846,9 +846,10 @@ configurationRegistry.registerConfiguration({ [NotebookSetting.showFoldingControls]: { description: nls.localize('notebook.showFoldingControls.description', "Controls when the Markdown header folding arrow is shown."), type: 'string', - enum: ['always', 'mouseover'], + enum: ['always', 'never', 'mouseover'], enumDescriptions: [ nls.localize('showFoldingControls.always', "The folding controls are always visible."), + nls.localize('showFoldingControls.never', "Never show the folding controls and reduce the gutter size."), nls.localize('showFoldingControls.mouseover', "The folding controls are visible only on mouseover."), ], default: 'mouseover', diff --git a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts index 426871f84fa..2aa4ca14cf8 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts @@ -59,7 +59,7 @@ export interface NotebookLayoutConfiguration { globalToolbar: boolean; consolidatedOutputButton: boolean; consolidatedRunButton: boolean; - showFoldingControls: 'always' | 'mouseover'; + showFoldingControls: 'always' | 'never' | 'mouseover'; dragAndDropEnabled: boolean; fontSize: number; outputFontSize: number; @@ -385,7 +385,7 @@ export class NotebookOptions extends Disposable { } private _computeShowFoldingControlsOption() { - return this.configurationService.getValue<'always' | 'mouseover'>(NotebookSetting.showFoldingControls) ?? 'mouseover'; + return this.configurationService.getValue<'always' | 'never' | 'mouseover'>(NotebookSetting.showFoldingControls) ?? 'mouseover'; } private _computeFocusIndicatorOption() { -- cgit v1.2.3 From a5ed786a96d431daa9d85fb832f6ac1b799938da Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 7 Jul 2022 05:15:51 -0700 Subject: Revert "more specific setting names" This reverts commit 2d9947ba4e53c0889c07e3a415572ec8bad1529e. --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- .../contrib/terminal/browser/terminalProfileResolverService.ts | 4 ++-- src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index dbba638fbd9..527493739e4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -550,7 +550,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _getIcon(): TerminalIcon | undefined { if (!this._icon) { this._icon = this._processManager.processState >= ProcessState.Launching - ? getIconRegistry().getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIconId)) + ? getIconRegistry().getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)) : undefined; } return this._icon; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts index b133c8e5d8d..f920f87442f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts @@ -117,7 +117,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro } getDefaultIcon(): TerminalIcon & ThemeIcon { - return this._iconRegistry.getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIconId)) || Codicon.terminal; + return this._iconRegistry.getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)) || Codicon.terminal; } async resolveShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig, options: IShellLaunchConfigResolveOptions): Promise { @@ -157,7 +157,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro // Apply the color shellLaunchConfig.color = shellLaunchConfig.color || resolvedProfile.color - || this._configurationService.getValue(TerminalSettingId.TabsDefaultIconColor); + || this._configurationService.getValue(TerminalSettingId.TabsDefaultColor); // Resolve useShellEnvironment based on the setting if it's not set if (shellLaunchConfig.useShellEnvironment === undefined) { diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 2675a545b60..9a63b3777a8 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -40,12 +40,12 @@ const terminalConfiguration: IConfigurationNode = { type: 'boolean', default: false }, - [TerminalSettingId.TabsDefaultIconColor]: { - description: localize('terminal.integrated.tabs.defaultIconColor', "A theme color ID to associate with terminals by default."), + [TerminalSettingId.TabsDefaultColor]: { + description: localize('terminal.integrated.tabs.defaultColor', "A theme color ID to associate with terminals by default."), ...terminalColorSchema }, - [TerminalSettingId.TabsDefaultIconId]: { - description: localize('terminal.integrated.tabs.defaultIconId', "A codicon ID to associate with terminals by default."), + [TerminalSettingId.TabsDefaultIcon]: { + description: localize('terminal.integrated.tabs.defaultIcon', "A codicon ID to associate with terminals by default."), ...terminalIconSchema, default: Codicon.terminal.id, }, -- cgit v1.2.3 From f7f3f48049934cf5d3a060aa911dafed5046500c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 7 Jul 2022 05:17:24 -0700 Subject: Mention icon in terminal icon/color settings Fixes #154354 --- src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 9a63b3777a8..3c5513e7c90 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -41,11 +41,11 @@ const terminalConfiguration: IConfigurationNode = { default: false }, [TerminalSettingId.TabsDefaultColor]: { - description: localize('terminal.integrated.tabs.defaultColor', "A theme color ID to associate with terminals by default."), + description: localize('terminal.integrated.tabs.defaultColor', "A theme color ID to associate with terminal icons by default."), ...terminalColorSchema }, [TerminalSettingId.TabsDefaultIcon]: { - description: localize('terminal.integrated.tabs.defaultIcon', "A codicon ID to associate with terminals by default."), + description: localize('terminal.integrated.tabs.defaultIcon', "A codicon ID to associate with terminal icons by default."), ...terminalIconSchema, default: Codicon.terminal.id, }, -- cgit v1.2.3 From d68ae55c9a9135461e948382900c88ef38e442b6 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 7 Jul 2022 08:38:13 -0400 Subject: show fit to content width in command palette (#154325) * fix #152748 * better solution Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> * Update src/vs/workbench/contrib/terminal/browser/terminalActions.ts * add comma Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index ee79622f1c2..c46f4d33c30 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -2127,10 +2127,11 @@ export function registerTerminalActions() { title: { value: localize('workbench.action.terminal.sizeToContentWidth', "Toggle Size to Content Width"), original: 'Toggle Size to Content Width' }, f1: true, category, - precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen, TerminalContextKeys.focus), + precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen), keybinding: { primary: KeyMod.Alt | KeyCode.KeyZ, - weight: KeybindingWeight.WorkbenchContrib + weight: KeybindingWeight.WorkbenchContrib, + when: TerminalContextKeys.focus } }); } @@ -2138,12 +2139,13 @@ export function registerTerminalActions() { await accessor.get(ITerminalService).doWithActiveInstance(t => t.toggleSizeToContentWidth()); } }); + registerAction2(class extends Action2 { constructor() { super({ id: TerminalCommandId.SizeToContentWidthInstance, title: terminalStrings.toggleSizeToContentWidth, - f1: true, + f1: false, category, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.focus) }); -- cgit v1.2.3 From 6cf558d7a0bf13ef6726bb5fe91219633a042094 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 7 Jul 2022 08:38:30 -0400 Subject: make task setting keys into an enum (#154322) --- .../contrib/tasks/browser/abstractTaskService.ts | 28 ++++++++++++---------- .../contrib/tasks/browser/task.contribution.ts | 20 ++++++++-------- .../contrib/tasks/browser/terminalTaskSystem.ts | 6 ++--- src/vs/workbench/contrib/tasks/common/tasks.ts | 23 ++++++++++++++++++ 4 files changed, 51 insertions(+), 26 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index da7e0dccf58..644103faf6a 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -53,7 +53,9 @@ import { ITaskSet, TaskGroup, ExecutionEngine, JsonSchemaVersion, TaskSourceKind, TaskSorter, ITaskIdentifier, TASK_RUNNING_STATE, TaskRunSource, KeyedTaskIdentifier as KeyedTaskIdentifier, TaskDefinition, RuntimeType, - USER_TASKS_GROUP_KEY + USER_TASKS_GROUP_KEY, + TaskSettingId, + TasksSchemaProperties } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskService, ITaskProvider, IProblemMatcherRunOptions, ICustomizationProperties, ITaskFilter, IWorkspaceFolderTaskResult, CustomExecutionSupportedContext, ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService'; import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates'; @@ -1093,7 +1095,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private _isProvideTasksEnabled(): boolean { - const settingValue = this._configurationService.getValue('task.autoDetect'); + const settingValue = this._configurationService.getValue(TaskSettingId.AutoDetect); return settingValue === 'on'; } @@ -1688,7 +1690,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer Prompt = 'prompt' } - const saveBeforeRunTaskConfig: SaveBeforeRunConfigOptions = this._configurationService.getValue('task.saveBeforeRun'); + const saveBeforeRunTaskConfig: SaveBeforeRunConfigOptions = this._configurationService.getValue(TaskSettingId.SaveBeforeRun); if (saveBeforeRunTaskConfig === SaveBeforeRunConfigOptions.Never) { return false; @@ -3523,11 +3525,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } const configTasks: (TaskConfig.ICustomTask | TaskConfig.IConfiguringTask)[] = []; - const suppressTaskName = !!this._configurationService.getValue('tasks.suppressTaskName', { resource: folder.uri }); + const suppressTaskName = !!this._configurationService.getValue(TasksSchemaProperties.SuppressTaskName, { resource: folder.uri }); const globalConfig = { - windows: this._configurationService.getValue('tasks.windows', { resource: folder.uri }), - osx: this._configurationService.getValue('tasks.osx', { resource: folder.uri }), - linux: this._configurationService.getValue('tasks.linux', { resource: folder.uri }) + windows: this._configurationService.getValue(TasksSchemaProperties.Windows, { resource: folder.uri }), + osx: this._configurationService.getValue(TasksSchemaProperties.Osx, { resource: folder.uri }), + linux: this._configurationService.getValue(TasksSchemaProperties.Linux, { resource: folder.uri }) }; tasks.get(folder).forEach(task => { const configTask = this._upgradeTask(task, suppressTaskName, globalConfig); @@ -3539,14 +3541,14 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._workspaceTasksPromise = undefined; await this._writeConfiguration(folder, 'tasks.tasks', configTasks); await this._writeConfiguration(folder, 'tasks.version', '2.0.0'); - if (this._configurationService.getValue('tasks.showOutput', { resource: folder.uri })) { - await this._configurationService.updateValue('tasks.showOutput', undefined, { resource: folder.uri }); + if (this._configurationService.getValue(TasksSchemaProperties.ShowOutput, { resource: folder.uri })) { + await this._configurationService.updateValue(TasksSchemaProperties.ShowOutput, undefined, { resource: folder.uri }); } - if (this._configurationService.getValue('tasks.isShellCommand', { resource: folder.uri })) { - await this._configurationService.updateValue('tasks.isShellCommand', undefined, { resource: folder.uri }); + if (this._configurationService.getValue(TasksSchemaProperties.IsShellCommand, { resource: folder.uri })) { + await this._configurationService.updateValue(TasksSchemaProperties.IsShellCommand, undefined, { resource: folder.uri }); } - if (this._configurationService.getValue('tasks.suppressTaskName', { resource: folder.uri })) { - await this._configurationService.updateValue('tasks.suppressTaskName', undefined, { resource: folder.uri }); + if (this._configurationService.getValue(TasksSchemaProperties.SuppressTaskName, { resource: folder.uri })) { + await this._configurationService.updateValue(TasksSchemaProperties.SuppressTaskName, undefined, { resource: folder.uri }); } } this._updateSetup(); diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index eff66ddba5f..911ee6ec393 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -20,7 +20,7 @@ import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatus import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/services/output/common/output'; -import { ITaskEvent, TaskEventKind, TaskGroup, TASKS_CATEGORY, TASK_RUNNING_STATE } from 'vs/workbench/contrib/tasks/common/tasks'; +import { ITaskEvent, TaskEventKind, TaskGroup, TaskSettingId, TASKS_CATEGORY, TASK_RUNNING_STATE } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskService, ProcessExecutionSupportedContext, ShellExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -431,7 +431,7 @@ configurationRegistry.registerConfiguration({ title: nls.localize('tasksConfigurationTitle', "Tasks"), type: 'object', properties: { - 'task.problemMatchers.neverPrompt': { + [TaskSettingId.ProblemMatchersNeverPrompt]: { markdownDescription: nls.localize('task.problemMatchers.neverPrompt', "Configures whether to show the problem matcher prompt when running a task. Set to `true` to never prompt, or use a dictionary of task types to turn off prompting only for specific task types."), 'oneOf': [ { @@ -453,13 +453,13 @@ configurationRegistry.registerConfiguration({ ], default: false }, - 'task.autoDetect': { + [TaskSettingId.AutoDetect]: { markdownDescription: nls.localize('task.autoDetect', "Controls enablement of `provideTasks` for all task provider extension. If the Tasks: Run Task command is slow, disabling auto detect for task providers may help. Individual extensions may also provide settings that disable auto detection."), type: 'string', enum: ['on', 'off'], default: 'on' }, - 'task.slowProviderWarning': { + [TaskSettingId.SlowProviderWarning]: { markdownDescription: nls.localize('task.slowProviderWarning', "Configures whether a warning is shown when a provider is slow"), 'oneOf': [ { @@ -476,32 +476,32 @@ configurationRegistry.registerConfiguration({ ], default: true }, - 'task.quickOpen.history': { + [TaskSettingId.QuickOpenHistory]: { markdownDescription: nls.localize('task.quickOpen.history', "Controls the number of recent items tracked in task quick open dialog."), type: 'number', default: 30, minimum: 0, maximum: 30 }, - 'task.quickOpen.detail': { + [TaskSettingId.QuickOpenDetail]: { markdownDescription: nls.localize('task.quickOpen.detail', "Controls whether to show the task detail for tasks that have a detail in task quick picks, such as Run Task."), type: 'boolean', default: true }, - 'task.quickOpen.skip': { + [TaskSettingId.QuickOpenSkip]: { type: 'boolean', description: nls.localize('task.quickOpen.skip', "Controls whether the task quick pick is skipped when there is only one task to pick from."), default: false }, - 'task.quickOpen.showAll': { + [TaskSettingId.QuickOpenShowAll]: { type: 'boolean', description: nls.localize('task.quickOpen.showAll', "Causes the Tasks: Run Task command to use the slower \"show all\" behavior instead of the faster two level picker where tasks are grouped by provider."), default: false }, - 'task.showDecorations': { + [TaskSettingId.ShowDecorations]: { type: 'boolean', description: nls.localize('task.showDecorations', "Shows decorations at points of interest in the terminal buffer such as the first problem found via a watch task. Note that this will only take effect for future tasks."), default: true }, - 'task.saveBeforeRun': { + [TaskSettingId.SaveBeforeRun]: { markdownDescription: nls.localize( 'task.saveBeforeRun', 'Save all dirty editors before running a task.' diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 0beaded25b4..754a84163c1 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -31,7 +31,7 @@ import { IOutputService } from 'vs/workbench/services/output/common/output'; import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors'; import { Task, CustomTask, ContributedTask, RevealKind, CommandOptions, IShellConfiguration, RuntimeType, PanelKind, - TaskEvent, TaskEventKind, IShellQuotingOptions, ShellQuoting, CommandString, ICommandConfiguration, IExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder, TaskSourceKind, InMemoryTask, ITaskEvent + TaskEvent, TaskEventKind, IShellQuotingOptions, ShellQuoting, CommandString, ICommandConfiguration, IExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder, TaskSourceKind, InMemoryTask, ITaskEvent, TaskSettingId } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver, @@ -209,10 +209,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { private readonly _onDidStateChange: Emitter; get taskShellIntegrationStartSequence(): string { - return this._configurationService.getValue('task.showDecorations') ? VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, `${VSCodeOscProperty.Task}=True`) + VSCodeSequence(VSCodeOscPt.CommandStart) : ''; + return this._configurationService.getValue(TaskSettingId.ShowDecorations) ? VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, `${VSCodeOscProperty.Task}=True`) + VSCodeSequence(VSCodeOscPt.CommandStart) : ''; } get taskShellIntegrationOutputSequence(): string { - return this._configurationService.getValue('task.showDecorations') ? VSCodeSequence(VSCodeOscPt.CommandExecuted) : ''; + return this._configurationService.getValue(TaskSettingId.ShowDecorations) ? VSCodeSequence(VSCodeOscPt.CommandExecuted) : ''; } constructor( diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index f4956cc9aef..2300c659cb0 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -1190,6 +1190,29 @@ export namespace KeyedTaskIdentifier { } } +export const enum TaskSettingId { + AutoDetect = 'task.autoDetect', + SaveBeforeRun = 'task.saveBeforeRun', + ShowDecorations = 'task.showDecorations', + ProblemMatchersNeverPrompt = 'task.problemMatchers.neverPrompt', + SlowProviderWarning = 'task.slowProviderWarning', + QuickOpenHistory = 'task.quickOpen.history', + QuickOpenDetail = 'task.quickOpen.detail', + QuickOpenSkip = 'task.quickOpen.skip', + QuickOpenShowAll = 'task.quickOpen.showAll' +} + +export const enum TasksSchemaProperties { + Tasks = 'tasks', + SuppressTaskName = 'tasks.suppressTaskName', + Windows = 'tasks.windows', + Osx = 'tasks.osx', + Linux = 'tasks.linux', + ShowOutput = 'tasks.showOutput', + IsShellCommand = 'tasks.isShellCommand', + ServiceTestSetting = 'tasks.service.testSetting', +} + export namespace TaskDefinition { export function createTaskIdentifier(external: ITaskIdentifier, reporter: { error(message: string): void }): KeyedTaskIdentifier | undefined { const definition = TaskDefinitionRegistry.get(external.type); -- cgit v1.2.3 From 30c88106734239138e6b875552ce4a90fef79386 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 7 Jul 2022 06:32:54 -0700 Subject: Use BASH_COMMAND instead of history for empty command detection Part of #143766 --- .../terminal/browser/media/shellIntegration-bash.sh | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index b0c525a298d..c57e7eb13db 100755 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -39,13 +39,6 @@ if [[ "$PROMPT_COMMAND" =~ .*(' '.*\;)|(\;.*' ').* ]]; then builtin return fi -# Disable shell integration if HISTCONTROL is set to erase duplicate entries as the exit code -# reporting relies on the duplicates existing -if [[ "$HISTCONTROL" =~ .*erasedups.* ]]; then - builtin unset VSCODE_SHELL_INTEGRATION - builtin return -fi - if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then builtin return fi @@ -56,7 +49,7 @@ __vsc_original_PS2="$PS2" __vsc_custom_PS1="" __vsc_custom_PS2="" __vsc_in_command_execution="1" -__vsc_last_history_id=$(history 1 | awk '{print $1;}') +__vsc_current_command="" __vsc_prompt_start() { builtin printf "\033]633;A\007" @@ -72,6 +65,13 @@ __vsc_update_cwd() { __vsc_command_output_start() { builtin printf "\033]633;C\007" + if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then + __vsc_current_command=$BASH_COMMAND + builtin printf "\033]633;E;$BASH_COMMAND\007" + else + __vsc_current_command="" + builtin printf "\033]633;E\007" + fi } __vsc_continuation_start() { @@ -83,12 +83,10 @@ __vsc_continuation_end() { } __vsc_command_complete() { - local __vsc_history_id=$(builtin history 1 | awk '{print $1;}') - if [[ "$__vsc_history_id" == "$__vsc_last_history_id" ]]; then + if [ "$__vsc_current_command" = "" ]; then builtin printf "\033]633;D\007" else builtin printf "\033]633;D;%s\007" "$__vsc_status" - __vsc_last_history_id=$__vsc_history_id fi __vsc_update_cwd } -- cgit v1.2.3 From f52f2ed7b5f50dac9aa71e899d12078c31020ca0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 7 Jul 2022 06:55:01 -0700 Subject: Use preexec $1 not history and send command Fixes #143766 --- .../terminal/browser/media/shellIntegration-bash.sh | 14 +++++++------- .../terminal/browser/media/shellIntegration-rc.zsh | 20 +++++++------------- 2 files changed, 14 insertions(+), 20 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index c57e7eb13db..88498fbfb9a 100755 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -65,13 +65,7 @@ __vsc_update_cwd() { __vsc_command_output_start() { builtin printf "\033]633;C\007" - if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then - __vsc_current_command=$BASH_COMMAND - builtin printf "\033]633;E;$BASH_COMMAND\007" - else - __vsc_current_command="" - builtin printf "\033]633;E\007" - fi + builtin printf "\033]633;E;$__vsc_current_command\007" } __vsc_continuation_start() { @@ -111,6 +105,7 @@ __vsc_update_prompt() { __vsc_precmd() { __vsc_command_complete "$__vsc_status" + __vsc_current_command="" __vsc_update_prompt } @@ -118,6 +113,11 @@ __vsc_preexec() { if [ "$__vsc_in_command_execution" = "0" ]; then __vsc_initialized=1 __vsc_in_command_execution="1" + if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then + __vsc_current_command=$BASH_COMMAND + else + __vsc_current_command="" + fi __vsc_command_output_start fi } diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh index 595c1261e18..7db2583a817 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh @@ -33,9 +33,8 @@ if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then builtin return fi -__vsc_initialized="0" __vsc_in_command_execution="1" -__vsc_last_history_id=0 +__vsc_current_command="" __vsc_prompt_start() { builtin printf "\033]633;A\007" @@ -51,6 +50,7 @@ __vsc_update_cwd() { __vsc_command_output_start() { builtin printf "\033]633;C\007" + builtin printf "\033]633;E;$__vsc_current_command\007" } __vsc_continuation_start() { @@ -70,17 +70,10 @@ __vsc_right_prompt_end() { } __vsc_command_complete() { - builtin local __vsc_history_id=$(builtin history | tail -n1 | awk '{print $1;}') - # Don't write the command complete sequence for the first prompt without an associated command - if [[ "$__vsc_initialized" == "1" ]]; then - if [[ "$__vsc_history_id" == "$__vsc_last_history_id" ]]; then - builtin printf "\033]633;D\007" - else - builtin printf "\033]633;D;%s\007" "$__vsc_status" - __vsc_last_history_id=$__vsc_history_id - fi - else + if [[ "$__vsc_current_command" == "" ]]; then builtin printf "\033]633;D\007" + else + builtin printf "\033]633;D;%s\007" "$__vsc_status" fi __vsc_update_cwd } @@ -112,6 +105,7 @@ __vsc_precmd() { fi __vsc_command_complete "$__vsc_status" + __vsc_current_command="" # in command execution if [ -n "$__vsc_in_command_execution" ]; then @@ -125,8 +119,8 @@ __vsc_preexec() { if [ -n "$RPROMPT" ]; then RPROMPT="$__vsc_prior_rprompt" fi - __vsc_initialized="1" __vsc_in_command_execution="1" + __vsc_current_command=$1 __vsc_command_output_start } add-zsh-hook precmd __vsc_precmd -- cgit v1.2.3 From 1b8ac1d09d6eea72e9305ba977d6449106251349 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 7 Jul 2022 17:40:22 +0200 Subject: Fix 2 clicks to show collapsed comments (#154365) Fixes #153924 --- src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts | 1 + src/vs/workbench/contrib/comments/browser/commentsView.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 335976fbac8..41ed64d6d55 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -382,6 +382,7 @@ export class CommentController implements IEditorContribution { this._commentingRangeDecorator.update(this.editor, []); this._commentThreadRangeDecorator.update(this.editor, []); dispose(this._commentWidgets); + this._commentWidgets = []; } })); diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index d6883919fa6..4c38db0a895 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -234,7 +234,7 @@ export class CommentsPanel extends ViewPane { const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment.uniqueIdInThread : element.comment.uniqueIdInThread; if (threadToReveal && isCodeEditor(editor)) { const controller = CommentController.get(editor); - controller?.revealCommentThread(threadToReveal, commentToReveal, false); + controller?.revealCommentThread(threadToReveal, commentToReveal, true); } return true; -- cgit v1.2.3 From 755d39f1e079d103fc7f6dce43e9bc00071f24fe Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 7 Jul 2022 13:58:38 -0400 Subject: add allow automatic tasks setting (#154171) --- .../contrib/tasks/browser/abstractTaskService.ts | 2 +- .../contrib/tasks/browser/runAutomaticTasks.ts | 48 ++++++++++++---------- .../contrib/tasks/browser/task.contribution.ts | 12 ++++++ src/vs/workbench/contrib/tasks/common/tasks.ts | 3 +- 4 files changed, 41 insertions(+), 24 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 644103faf6a..1a3261a3335 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -1084,7 +1084,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }).then((value) => { if (runSource === TaskRunSource.User) { this.getWorkspaceTasks().then(workspaceTasks => { - RunAutomaticTasks.promptForPermission(this, this._storageService, this._notificationService, this._workspaceTrustManagementService, this._openerService, workspaceTasks); + RunAutomaticTasks.promptForPermission(this, this._storageService, this._notificationService, this._workspaceTrustManagementService, this._openerService, this._configurationService, workspaceTasks); }); } return value; diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index 55d63c00125..aa0d0441b79 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -15,18 +15,19 @@ import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/commo import { Action2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; -import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { ILogService } from 'vs/platform/log/common/log'; -const ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE = 'tasks.run.allowAutomatic'; +const HAS_PROMPTED_FOR_AUTOMATIC_TASKS = 'task.hasPromptedForAutomaticTasks'; +const ALLOW_AUTOMATIC_TASKS = 'task.allowAutomaticTasks'; export class RunAutomaticTasks extends Disposable implements IWorkbenchContribution { constructor( @ITaskService private readonly _taskService: ITaskService, - @IStorageService private readonly _storageService: IStorageService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService, @ILogService private readonly _logService: ILogService) { super(); @@ -42,7 +43,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut } this._logService.trace('RunAutomaticTasks: Checking if automatic tasks should run.'); - const isFolderAutomaticAllowed = this._storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined); + const isFolderAutomaticAllowed = this._configurationService.getValue(ALLOW_AUTOMATIC_TASKS) !== 'off'; await this._workspaceTrustManagementService.workspaceTrustInitialized; const isWorkspaceTrusted = this._workspaceTrustManagementService.isWorkspaceTrusted(); // Only run if allowed. Prompting for permission occurs when a user first tries to run a task. @@ -128,30 +129,33 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut } public static async promptForPermission(taskService: ITaskService, storageService: IStorageService, notificationService: INotificationService, workspaceTrustManagementService: IWorkspaceTrustManagementService, - openerService: IOpenerService, workspaceTaskResult: Map) { + openerService: IOpenerService, configurationService: IConfigurationService, workspaceTaskResult: Map) { const isWorkspaceTrusted = workspaceTrustManagementService.isWorkspaceTrusted; if (!isWorkspaceTrusted) { return; } - - const isFolderAutomaticAllowed = storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined); - if (isFolderAutomaticAllowed !== undefined) { + if (configurationService.getValue(ALLOW_AUTOMATIC_TASKS) === 'off') { return; } + const hasShownPromptForAutomaticTasks = storageService.getBoolean(HAS_PROMPTED_FOR_AUTOMATIC_TASKS, StorageScope.WORKSPACE, undefined); const { tasks, taskNames, locations } = RunAutomaticTasks._findAutoTasks(taskService, workspaceTaskResult); if (taskNames.length > 0) { - // We have automatic tasks, prompt to allow. - this._showPrompt(notificationService, storageService, taskService, openerService, taskNames, locations).then(allow => { - if (allow) { - RunAutomaticTasks._runTasks(taskService, tasks); - } - }); + if (configurationService.getValue(ALLOW_AUTOMATIC_TASKS) === 'on') { + RunAutomaticTasks._runTasks(taskService, tasks); + } else if (!hasShownPromptForAutomaticTasks) { + // We have automatic tasks, prompt to allow. + this._showPrompt(notificationService, storageService, openerService, configurationService, taskNames, locations).then(allow => { + if (allow) { + RunAutomaticTasks._runTasks(taskService, tasks); + } + }); + } } } - private static _showPrompt(notificationService: INotificationService, storageService: IStorageService, taskService: ITaskService, - openerService: IOpenerService, taskNames: Array, locations: Map): Promise { + private static _showPrompt(notificationService: INotificationService, storageService: IStorageService, + openerService: IOpenerService, configurationService: IConfigurationService, taskNames: Array, locations: Map): Promise { return new Promise(resolve => { notificationService.prompt(Severity.Info, nls.localize('tasks.run.allowAutomatic', "This workspace has tasks ({0}) defined ({1}) that run automatically when you open this workspace. Do you allow automatic tasks to run when you open this workspace?", @@ -162,14 +166,15 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut label: nls.localize('allow', "Allow and run"), run: () => { resolve(true); - storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); + configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, true, ConfigurationTarget.WORKSPACE); } }, { label: nls.localize('disallow', "Disallow"), run: () => { resolve(false); - storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, false, StorageScope.WORKSPACE, StorageTarget.MACHINE); + configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, false, ConfigurationTarget.WORKSPACE); + } }, { @@ -182,9 +187,9 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut } }] ); + storageService.store(HAS_PROMPTED_FOR_AUTOMATIC_TASKS, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); }); } - } export class ManageAutomaticTaskRunning extends Action2 { @@ -202,14 +207,13 @@ export class ManageAutomaticTaskRunning extends Action2 { public async run(accessor: ServicesAccessor): Promise { const quickInputService = accessor.get(IQuickInputService); - const storageService = accessor.get(IStorageService); + const configurationService = accessor.get(IConfigurationService); const allowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.allowAutomaticTasks', "Allow Automatic Tasks in Folder") }; const disallowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.disallowAutomaticTasks', "Disallow Automatic Tasks in Folder") }; const value = await quickInputService.pick([allowItem, disallowItem], { canPickMany: false }); if (!value) { return; } - - storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, value === allowItem, StorageScope.WORKSPACE, StorageTarget.MACHINE); + configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, value === allowItem, ConfigurationTarget.WORKSPACE); } } diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 911ee6ec393..00c85294b7b 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -496,6 +496,18 @@ configurationRegistry.registerConfiguration({ description: nls.localize('task.quickOpen.showAll', "Causes the Tasks: Run Task command to use the slower \"show all\" behavior instead of the faster two level picker where tasks are grouped by provider."), default: false }, + [TaskSettingId.AllowAutomaticTasks]: { + type: 'string', + enum: ['on', 'auto', 'off'], + enumDescriptions: [ + nls.localize('ttask.allowAutomaticTasks.on', "Always"), + nls.localize('task.allowAutomaticTasks.auto', "Prompt for permission for each folder"), + nls.localize('task.allowAutomaticTasks.off', "Never"), + ], + description: nls.localize('task.allowAutomaticTasks', "Enable automatic tasks in the folder."), + default: 'auto', + restricted: true + }, [TaskSettingId.ShowDecorations]: { type: 'boolean', description: nls.localize('task.showDecorations', "Shows decorations at points of interest in the terminal buffer such as the first problem found via a watch task. Note that this will only take effect for future tasks."), diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 2300c659cb0..5877beb6437 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -1199,7 +1199,8 @@ export const enum TaskSettingId { QuickOpenHistory = 'task.quickOpen.history', QuickOpenDetail = 'task.quickOpen.detail', QuickOpenSkip = 'task.quickOpen.skip', - QuickOpenShowAll = 'task.quickOpen.showAll' + QuickOpenShowAll = 'task.quickOpen.showAll', + AllowAutomaticTasks = 'task.allowAutomaticTasks' } export const enum TasksSchemaProperties { -- cgit v1.2.3 From 1cc52ae41064ad363a214e7e0c67921b64e9c8df Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 7 Jul 2022 11:01:16 -0700 Subject: Fix broken Not Synced indicator (#154381) Fixes #154379 --- .../preferences/browser/settingsEditorSettingIndicators.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index a10179c4ac7..bbb035a2c99 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -51,12 +51,6 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { this.indicatorsContainerElement = DOM.append(container, $('.misc-label')); this.indicatorsContainerElement.style.display = 'inline'; - const scopeOverridesIndicator = this.createScopeOverridesIndicator(); - this.scopeOverridesElement = scopeOverridesIndicator.element; - this.scopeOverridesLabel = scopeOverridesIndicator.label; - this.syncIgnoredElement = this.createSyncIgnoredElement(); - this.defaultOverrideIndicatorElement = this.createDefaultOverrideIndicator(); - this.hoverDelegate = { showHover: (options: IHoverDelegateOptions, focus?: boolean) => { return hoverService.showHover(options, focus); @@ -65,6 +59,12 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { delay: configurationService.getValue('workbench.hover.delay'), placement: 'element' }; + + const scopeOverridesIndicator = this.createScopeOverridesIndicator(); + this.scopeOverridesElement = scopeOverridesIndicator.element; + this.scopeOverridesLabel = scopeOverridesIndicator.label; + this.syncIgnoredElement = this.createSyncIgnoredElement(); + this.defaultOverrideIndicatorElement = this.createDefaultOverrideIndicator(); } private createScopeOverridesIndicator(): { element: HTMLElement; label: SimpleIconLabel } { -- cgit v1.2.3 From 368400c1907a62a920b7a94ee10ff74a083f34c5 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Thu, 7 Jul 2022 11:01:33 -0700 Subject: Check save settings before debug restart (#154206) * Fix bug: Files automatically saved when restarting debugger, even though it is set not to Fixes #149885 --- src/vs/workbench/contrib/debug/browser/debugCommands.ts | 2 +- src/vs/workbench/contrib/debug/browser/debugService.ts | 10 ++++++++-- src/vs/workbench/contrib/debug/browser/debugSession.ts | 4 ++++ src/vs/workbench/contrib/debug/common/debug.ts | 4 +++- src/vs/workbench/contrib/debug/test/browser/mockDebug.ts | 4 ++++ 5 files changed, 20 insertions(+), 4 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 25da3464ac6..596bbd9c32d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -723,7 +723,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const { launch, name, getConfig } = debugService.getConfigurationManager().selectedConfiguration; const config = await getConfig(); const configOrName = config ? Object.assign(deepClone(config), debugStartOptions?.config) : name; - await debugService.startDebugging(launch, configOrName, { noDebug: debugStartOptions?.noDebug, startedByUser: true }, false); + await debugService.startDebugging(launch, configOrName, { noDebug: debugStartOptions?.noDebug, startedByUser: true, saveBeforeStart: false }); } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index b308716a8bb..2474922a5ad 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -312,7 +312,10 @@ export class DebugService implements IDebugService { * main entry point * properly manages compounds, checks for errors and handles the initializing state. */ - async startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions, saveBeforeStart = !options?.parentSession): Promise { + async startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise { + + const saveBeforeStart = options?.saveBeforeStart ?? !options?.parentSession; + const message = options && options.noDebug ? nls.localize('runTrust', "Running executes build tasks and program code from your workspace.") : nls.localize('debugTrust', "Debugging executes build tasks and program code from your workspace."); const trust = await this.workspaceTrustRequestService.requestWorkspaceTrust({ message }); if (!trust) { @@ -701,7 +704,10 @@ export class DebugService implements IDebugService { } async restartSession(session: IDebugSession, restartData?: any): Promise { - await this.editorService.saveAll(); + if (session.saveBeforeStart) { + await saveAllBeforeDebugStart(this.configurationService, this.editorService); + } + const isAutoRestart = !!restartData; const runTasks: () => Promise = async () => { diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index de26f713ef0..29a568d478b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -164,6 +164,10 @@ export class DebugSession implements IDebugSession { return !!this._options.compact; } + get saveBeforeStart(): boolean { + return this._options.saveBeforeStart ?? !this._options?.parentSession; + } + get compoundRoot(): DebugCompoundRoot | undefined { return this._options.compoundRoot; } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index a8397f69aef..21dc92d475c 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -206,6 +206,7 @@ export interface IDebugSessionOptions { simple?: boolean; }; startedByUser?: boolean; + saveBeforeStart?: boolean; } export interface IDataBreakpointInfoResponse { @@ -296,6 +297,7 @@ export interface IDebugSession extends ITreeElement { readonly subId: string | undefined; readonly compact: boolean; readonly compoundRoot: DebugCompoundRoot | undefined; + readonly saveBeforeStart: boolean; readonly name: string; readonly isSimpleUI: boolean; readonly autoExpandLazyVariables: boolean; @@ -1088,7 +1090,7 @@ export interface IDebugService { * Returns true if the start debugging was successful. For compound launches, all configurations have to start successfully for it to return success. * On errors the startDebugging will throw an error, however some error and cancelations are handled and in that case will simply return false. */ - startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions, saveBeforeStart?: boolean): Promise; + startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise; /** * Restarts a session or creates a new one if there is no active session. diff --git a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts index 1bebaa31adb..1596e346e31 100644 --- a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts @@ -190,6 +190,10 @@ export class MockSession implements IDebugSession { return undefined; } + get saveBeforeStart(): boolean { + return true; + } + get isSimpleUI(): boolean { return false; } -- cgit v1.2.3 From d5379b5e34e4bb0f5ddb33688da91f787ebc159f Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 7 Jul 2022 11:14:03 -0700 Subject: Fix more settings description setting links (#154383) Ref #154317 --- src/vs/workbench/contrib/files/browser/files.contribution.ts | 6 +++--- src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts | 6 +++--- src/vs/workbench/contrib/remote/common/remote.contribution.ts | 4 ++-- src/vs/workbench/contrib/search/browser/search.contribution.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 592d559451a..0db2db173e1 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -185,7 +185,7 @@ configurationRegistry.registerConfiguration({ 'files.autoGuessEncoding': { 'type': 'boolean', 'default': false, - 'markdownDescription': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language. Note, this setting is not respected by text search. Only `#files.encoding#` is respected."), + 'markdownDescription': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language. Note, this setting is not respected by text search. Only {0} is respected.", '`#files.encoding#`'), 'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE }, 'files.eol': { @@ -475,7 +475,7 @@ configurationRegistry.registerConfiguration({ }, 'explorer.excludeGitIgnore': { type: 'boolean', - markdownDescription: nls.localize('excludeGitignore', "Controls whether entries in .gitignore should be parsed and excluded from the explorer. Similar to `#files.exclude#`."), + markdownDescription: nls.localize('excludeGitignore', "Controls whether entries in .gitignore should be parsed and excluded from the explorer. Similar to {0}.", '`#files.exclude#`'), default: false, scope: ConfigurationScope.RESOURCE }, @@ -487,7 +487,7 @@ configurationRegistry.registerConfiguration({ }, 'explorer.fileNesting.expand': { 'type': 'boolean', - 'markdownDescription': nls.localize('fileNestingExpand', "Controls whether file nests are automatically expanded. `#explorer.fileNesting.enabled#` must be set for this to take effect."), + 'markdownDescription': nls.localize('fileNestingExpand', "Controls whether file nests are automatically expanded. {0} must be set for this to take effect.", '`#explorer.fileNesting.enabled#`'), 'default': true, }, 'explorer.fileNesting.patterns': { diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 6111d9f1caa..33bf079467f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -881,7 +881,7 @@ configurationRegistry.registerConfiguration({ tags: ['notebookLayout'] }, [NotebookSetting.markupFontSize]: { - markdownDescription: nls.localize('notebook.markup.fontSize', "Controls the font size in pixels of rendered markup in notebooks. When set to `0`, 120% of `#editor.fontSize#` is used."), + markdownDescription: nls.localize('notebook.markup.fontSize', "Controls the font size in pixels of rendered markup in notebooks. When set to {0}, 120% of {1} is used.", '`0`', '`#editor.fontSize#`'), type: 'number', default: 0, tags: ['notebookLayout'] @@ -900,13 +900,13 @@ configurationRegistry.registerConfiguration({ tags: ['notebookLayout'] }, [NotebookSetting.outputFontSize]: { - markdownDescription: nls.localize('notebook.outputFontSize', "Font size for the output text for notebook cells. When set to 0 `#editor.fontSize#` is used."), + markdownDescription: nls.localize('notebook.outputFontSize', "Font size for the output text for notebook cells. When set to {0}, {1} is used.", '`0`', '`#editor.fontSize#`'), type: 'number', default: 0, tags: ['notebookLayout'] }, [NotebookSetting.outputFontFamily]: { - markdownDescription: nls.localize('notebook.outputFontFamily', "The font family for the output text for notebook cells. When set to empty, the `#editor.fontFamily#` is used."), + markdownDescription: nls.localize('notebook.outputFontFamily', "The font family for the output text for notebook cells. When set to empty, the {0} is used.", '`#editor.fontFamily#`'), type: 'string', tags: ['notebookLayout'] }, diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index b6488780711..0b2e94b01bc 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -353,7 +353,7 @@ Registry.as(ConfigurationExtensions.Configuration) }, 'remote.autoForwardPortsSource': { type: 'string', - markdownDescription: localize('remote.autoForwardPortsSource', "Sets the source from which ports are automatically forwarded when `remote.autoForwardPorts` is true. On Windows and Mac remotes, the `process` option has no effect and `output` will be used. Requires a reload to take effect."), + markdownDescription: localize('remote.autoForwardPortsSource', "Sets the source from which ports are automatically forwarded when {0} is true. On Windows and Mac remotes, the `process` option has no effect and `output` will be used. Requires a reload to take effect.", '`#remote.autoForwardPorts#`'), enum: ['process', 'output'], enumDescriptions: [ localize('remote.autoForwardPortsSource.process', "Ports will be automatically forwarded when discovered by watching for processes that are started and include a port."), @@ -463,7 +463,7 @@ Registry.as(ConfigurationExtensions.Configuration) } }, defaultSnippets: [{ body: { onAutoForward: 'ignore' } }], - markdownDescription: localize('remote.portsAttributes.defaults', "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```"), + markdownDescription: localize('remote.portsAttributes.defaults', "Set default properties that are applied to all ports that don't get properties from the setting {0}. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", '`#remote.portsAttributes#`'), additionalProperties: false }, 'remote.localPortHost': { diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 08f7dc2cf25..54450eb71c4 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -996,7 +996,7 @@ configurationRegistry.registerConfiguration({ 'search.searchOnTypeDebouncePeriod': { type: 'number', default: 300, - markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When `#search.searchOnType#` is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when `search.searchOnType` is disabled.") + markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When {0} is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when {0} is disabled.", '`#search.searchOnType#`') }, 'search.searchEditor.doubleClickBehaviour': { type: 'string', -- cgit v1.2.3 From bc0bdc5d0969030f114d5931b18e134059da00c1 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 7 Jul 2022 11:55:35 -0700 Subject: Fix hover underline issue on "Modified in" label (#154386) Fixes #154385 --- src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css | 2 +- .../contrib/preferences/browser/settingsEditorSettingIndicators.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index d44eec7e5d5..11f242b2646 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -351,7 +351,7 @@ font-style: italic; } -.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-overrides:hover, +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-overrides.with-custom-hover:hover, .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-ignored:hover, .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-default-overridden:hover { text-decoration: underline; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts index bbb035a2c99..ae71ad571f6 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts @@ -139,6 +139,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { // Render inline if we have the flag and there are scope overrides to render, // or if there is only one scope override to render and no language overrides. this.scopeOverridesElement.style.display = 'inline'; + this.scopeOverridesElement.classList.remove('with-custom-hover'); this.hover?.dispose(); // Just show all the text in the label. @@ -170,6 +171,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable { // show the text in a custom hover only if // the feature flag isn't on. this.scopeOverridesElement.style.display = 'inline'; + this.scopeOverridesElement.classList.add('with-custom-hover'); const scopeOverridesLabelText = element.isConfigured ? localize('alsoConfiguredElsewhere', "Also modified elsewhere") : localize('configuredElsewhere', "Modified elsewhere"); -- cgit v1.2.3 From 00eb9c4356c520299e7e2c95eb2089698b30b7fd Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 7 Jul 2022 13:38:32 -0700 Subject: Polish up restart dialogs and switch to using Language instead of language (#154382) --- .../contrib/localization/browser/localeService.ts | 51 ++++++++++++++++----- .../localization/browser/localizationsActions.ts | 39 +--------------- .../contrib/localization/common/locale.ts | 4 +- .../localization/electron-sandbox/localeService.ts | 52 ++++++++++++++++------ 4 files changed, 83 insertions(+), 63 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/localization/browser/localeService.ts b/src/vs/workbench/contrib/localization/browser/localeService.ts index c59d84821b2..4768a1752b4 100644 --- a/src/vs/workbench/contrib/localization/browser/localeService.ts +++ b/src/vs/workbench/contrib/localization/browser/localeService.ts @@ -3,31 +3,62 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { language } from 'vs/base/common/platform'; +import { localize } from 'vs/nls'; +import { Language } from 'vs/base/common/platform'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { ILanguagePackItem } from 'vs/platform/languagePacks/common/languagePacks'; import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IProductService } from 'vs/platform/product/common/productService'; export class WebLocaleService implements ILocaleService { declare readonly _serviceBrand: undefined; - async setLocale(languagePackItem: ILanguagePackItem): Promise { + constructor( + @IDialogService private readonly dialogService: IDialogService, + @IHostService private readonly hostService: IHostService, + @IProductService private readonly productService: IProductService + ) { } + + async setLocale(languagePackItem: ILanguagePackItem): Promise { const locale = languagePackItem.id; - if (locale === language || (!locale && language === navigator.language)) { - return false; + if (locale === Language.value() || (!locale && Language.value() === navigator.language)) { + return; } if (locale) { window.localStorage.setItem('vscode.nls.locale', locale); } else { window.localStorage.removeItem('vscode.nls.locale'); } - return true; - } - async clearLocalePreference(): Promise { - if (language === navigator.language) { - return false; + const restartDialog = await this.dialogService.confirm({ + type: 'info', + message: localize('relaunchDisplayLanguageMessage', "{0} needs to reload to change the display language", this.productService.nameLong), + detail: localize('relaunchDisplayLanguageDetail', "Press the reload button to refresh the page and set the display language to {0}.", languagePackItem.label), + primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, "&&Reload"), + }); + + if (restartDialog.confirmed) { + this.hostService.restart(); } + } + + async clearLocalePreference(): Promise { window.localStorage.removeItem('vscode.nls.locale'); - return true; + + if (Language.value() === navigator.language) { + return; + } + + const restartDialog = await this.dialogService.confirm({ + type: 'info', + message: localize('clearDisplayLanguageMessage', "{0} needs to reload to change the display language", this.productService.nameLong), + detail: localize('clearDisplayLanguageDetail', "Press the reload button to refresh the page and use your browser's language."), + primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, "&&Reload"), + }); + + if (restartDialog.confirmed) { + this.hostService.restart(); + } } } diff --git a/src/vs/workbench/contrib/localization/browser/localizationsActions.ts b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts index 5bdb7500724..2be6435942d 100644 --- a/src/vs/workbench/contrib/localization/browser/localizationsActions.ts +++ b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts @@ -5,9 +5,6 @@ import { localize } from 'vs/nls'; import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IProductService } from 'vs/platform/product/common/productService'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; @@ -15,8 +12,6 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { ILanguagePackItem, ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; -const restart = localize('restart', "&&Restart"); - export class ConfigureDisplayLanguageAction extends Action2 { public static readonly ID = 'workbench.action.configureLocale'; public static readonly LABEL = localize('configureLocale', "Configure Display Language"); @@ -34,9 +29,6 @@ export class ConfigureDisplayLanguageAction extends Action2 { public async run(accessor: ServicesAccessor): Promise { const languagePackService: ILanguagePackService = accessor.get(ILanguagePackService); const quickInputService: IQuickInputService = accessor.get(IQuickInputService); - const hostService: IHostService = accessor.get(IHostService); - const dialogService: IDialogService = accessor.get(IDialogService); - const productService: IProductService = accessor.get(IProductService); const localeService: ILocaleService = accessor.get(ILocaleService); const installedLanguages = await languagePackService.getInstalledLanguages(); @@ -72,19 +64,7 @@ export class ConfigureDisplayLanguageAction extends Action2 { disposables.add(qp.onDidAccept(async () => { const selectedLanguage = qp.activeItems[0]; qp.hide(); - - if (await localeService.setLocale(selectedLanguage)) { - const restartDialog = await dialogService.confirm({ - type: 'info', - message: localize('relaunchDisplayLanguageMessage', "A restart is required for the change in display language to take effect."), - detail: localize('relaunchDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", productService.nameLong), - primaryButton: restart - }); - - if (restartDialog.confirmed) { - hostService.restart(); - } - } + await localeService.setLocale(selectedLanguage); })); qp.show(); @@ -108,21 +88,6 @@ export class ClearDisplayLanguageAction extends Action2 { public async run(accessor: ServicesAccessor): Promise { const localeService: ILocaleService = accessor.get(ILocaleService); - const dialogService: IDialogService = accessor.get(IDialogService); - const productService: IProductService = accessor.get(IProductService); - const hostService: IHostService = accessor.get(IHostService); - - if (await localeService.clearLocalePreference()) { - const restartDialog = await dialogService.confirm({ - type: 'info', - message: localize('relaunchAfterClearDisplayLanguageMessage', "A restart is required for the change in display language to take effect."), - detail: localize('relaunchAfterClearDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", productService.nameLong), - primaryButton: restart - }); - - if (restartDialog.confirmed) { - hostService.restart(); - } - } + await localeService.clearLocalePreference(); } } diff --git a/src/vs/workbench/contrib/localization/common/locale.ts b/src/vs/workbench/contrib/localization/common/locale.ts index f447d40bc41..f2e8417c443 100644 --- a/src/vs/workbench/contrib/localization/common/locale.ts +++ b/src/vs/workbench/contrib/localization/common/locale.ts @@ -10,6 +10,6 @@ export const ILocaleService = createDecorator('localizationServi export interface ILocaleService { readonly _serviceBrand: undefined; - setLocale(languagePackItem: ILanguagePackItem): Promise; - clearLocalePreference(): Promise; + setLocale(languagePackItem: ILanguagePackItem): Promise; + clearLocalePreference(): Promise; } diff --git a/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts b/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts index 00a6ec7036f..59b10dc5fcf 100644 --- a/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts +++ b/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { language } from 'vs/base/common/platform'; +import { Language } from 'vs/base/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; @@ -19,6 +19,9 @@ import { toAction } from 'vs/base/common/actions'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { stripComments } from 'vs/base/common/stripComments'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IProductService } from 'vs/platform/product/common/productService'; export class NativeLocaleService implements ILocaleService { _serviceBrand: undefined; @@ -32,7 +35,10 @@ export class NativeLocaleService implements ILocaleService { @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IProgressService private readonly progressService: IProgressService, @ITextFileService private readonly textFileService: ITextFileService, - @IEditorService private readonly editorService: IEditorService + @IEditorService private readonly editorService: IEditorService, + @IDialogService private readonly dialogService: IDialogService, + @IHostService private readonly hostService: IHostService, + @IProductService private readonly productService: IProductService ) { } private async validateLocaleFile(): Promise { @@ -69,10 +75,10 @@ export class NativeLocaleService implements ILocaleService { return true; } - async setLocale(languagePackItem: ILanguagePackItem): Promise { + async setLocale(languagePackItem: ILanguagePackItem): Promise { const locale = languagePackItem.id; - if (locale === language || (!locale && language === 'en')) { - return false; + if (locale === Language.value() || (!locale && Language.isDefaultVariant())) { + return; } const installedLanguages = await this.languagePackService.getInstalledLanguages(); try { @@ -87,7 +93,7 @@ export class NativeLocaleService implements ILocaleService { // as of now, there are no 3rd party language packs available on the Marketplace. const viewlet = await this.paneCompositePartService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar); (viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer).search(`@id:${languagePackItem.extensionId}`); - return false; + return; } await this.progressService.withProgress( @@ -102,22 +108,40 @@ export class NativeLocaleService implements ILocaleService { ); } - return await this.writeLocaleValue(locale); + if (await this.writeLocaleValue(locale)) { + await this.showRestartDialog(languagePackItem.label); + } } catch (err) { this.notificationService.error(err); - return false; } } - async clearLocalePreference(): Promise { - if (language === 'en') { - return false; - } + async clearLocalePreference(): Promise { try { - return await this.writeLocaleValue(undefined); + await this.writeLocaleValue(undefined); + if (!Language.isDefaultVariant()) { + await this.showRestartDialog('English'); + } } catch (err) { this.notificationService.error(err); - return false; + } + } + + private async showRestartDialog(languageName: string) { + const restartDialog = await this.dialogService.confirm({ + type: 'info', + message: localize('restartDisplayLanguageMessage', "{0} needs to restart to change the display language", this.productService.nameLong), + detail: localize( + 'restartDisplayLanguageDetail', + "Press the restart button to restart {0} and set the display language to {1}.", + this.productService.nameLong, + languageName + ), + primaryButton: localize({ key: 'restart', comment: ['&& denotes a mnemonic character'] }, "&&Restart"), + }); + + if (restartDialog.confirmed) { + this.hostService.restart(); } } } -- cgit v1.2.3 From 8fdc7542e99265a07f3bc6e512d074c4c0fa8721 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 7 Jul 2022 17:39:03 -0700 Subject: Hide split/kill when tabs never hide Fixes #154411 --- src/vs/workbench/contrib/terminal/browser/terminalMenus.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index 5183542cb14..c6e6700e280 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -417,6 +417,7 @@ export function setupTerminalMenus(): void { order: 2, when: ContextKeyExpr.and( ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), + ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'), ContextKeyExpr.or( ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`), ContextKeyExpr.and( @@ -451,6 +452,7 @@ export function setupTerminalMenus(): void { order: 3, when: ContextKeyExpr.and( ContextKeyExpr.equals('view', TERMINAL_VIEW_ID), + ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'), ContextKeyExpr.or( ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`), ContextKeyExpr.and( -- cgit v1.2.3 From 3104db414c8fedcf6e4493f14da7df0b7413853a Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 7 Jul 2022 21:19:59 -0700 Subject: slight copy change for display language dialog (#154402) slight copy change --- src/vs/workbench/contrib/localization/browser/localeService.ts | 4 ++-- .../workbench/contrib/localization/electron-sandbox/localeService.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/localization/browser/localeService.ts b/src/vs/workbench/contrib/localization/browser/localeService.ts index 4768a1752b4..dc8138589bf 100644 --- a/src/vs/workbench/contrib/localization/browser/localeService.ts +++ b/src/vs/workbench/contrib/localization/browser/localeService.ts @@ -33,7 +33,7 @@ export class WebLocaleService implements ILocaleService { const restartDialog = await this.dialogService.confirm({ type: 'info', - message: localize('relaunchDisplayLanguageMessage', "{0} needs to reload to change the display language", this.productService.nameLong), + message: localize('relaunchDisplayLanguageMessage', "To change the display language, {0} needs to reload", this.productService.nameLong), detail: localize('relaunchDisplayLanguageDetail', "Press the reload button to refresh the page and set the display language to {0}.", languagePackItem.label), primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, "&&Reload"), }); @@ -52,7 +52,7 @@ export class WebLocaleService implements ILocaleService { const restartDialog = await this.dialogService.confirm({ type: 'info', - message: localize('clearDisplayLanguageMessage', "{0} needs to reload to change the display language", this.productService.nameLong), + message: localize('clearDisplayLanguageMessage', "To change the display language, {0} needs to reload", this.productService.nameLong), detail: localize('clearDisplayLanguageDetail', "Press the reload button to refresh the page and use your browser's language."), primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, "&&Reload"), }); diff --git a/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts b/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts index 59b10dc5fcf..4c0da0bc253 100644 --- a/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts +++ b/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts @@ -130,7 +130,7 @@ export class NativeLocaleService implements ILocaleService { private async showRestartDialog(languageName: string) { const restartDialog = await this.dialogService.confirm({ type: 'info', - message: localize('restartDisplayLanguageMessage', "{0} needs to restart to change the display language", this.productService.nameLong), + message: localize('restartDisplayLanguageMessage', "To change the display language, {0} needs to restart", this.productService.nameLong), detail: localize( 'restartDisplayLanguageDetail', "Press the restart button to restart {0} and set the display language to {1}.", -- cgit v1.2.3 From ac70882663dd94883d9b86579b408df02aa3af24 Mon Sep 17 00:00:00 2001 From: jeanp413 Date: Thu, 7 Jul 2022 23:42:43 -0500 Subject: :lipstick: --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index de3acb2313b..5a98560dae0 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1368,7 +1368,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._pressAnyKeyToCloseListener = undefined; } - this._exitReason = reason || TerminalExitReason.Unknown; + if (typeof this._exitReason === 'undefined') { + this._exitReason = reason ?? TerminalExitReason.Unknown; + } + this._processManager.dispose(); // Process manager dispose/shutdown doesn't fire process exit, trigger with undefined if it // hasn't happened yet -- cgit v1.2.3 From 37c6c1ce340026317de409b110f9a1f19ffc55a8 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 8 Jul 2022 08:41:57 +0200 Subject: editors - let editors fully control confirmation on close (#152841) --- .../mergeEditor/browser/mergeEditorInput.ts | 23 +++++++++++----------- .../terminal/browser/terminalEditorInput.ts | 12 ++++++++--- 2 files changed, 21 insertions(+), 14 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 053c7309754..377dc53be55 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -14,7 +14,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput'; import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; @@ -22,7 +22,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { assertType } from 'vs/base/common/types'; -import { Event } from 'vs/base/common/event'; export class MergeEditorInputData { constructor( @@ -33,7 +32,7 @@ export class MergeEditorInputData { ) { } } -export class MergeEditorInput extends AbstractTextResourceEditorInput implements ILanguageSupport { +export class MergeEditorInput extends AbstractTextResourceEditorInput implements ILanguageSupport, IEditorCloseHandler { static readonly ID = 'mergeEditor.Input'; @@ -125,8 +124,6 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements this._store.add(input1); this._store.add(input2); this._store.add(result); - - this._store.add(Event.fromObservable(this._model.hasUnhandledConflicts)(() => this._onDidChangeDirty.fire(undefined))); } this._ignoreUnhandledConflictsForDirtyState = undefined; @@ -146,21 +143,25 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements // ---- FileEditorInput override isDirty(): boolean { - const textModelDirty = Boolean(this._outTextModel?.isDirty()); - if (textModelDirty) { + return Boolean(this._outTextModel?.isDirty()); + } + + override readonly closeHandler = this; + + showConfirm(): boolean { + if (this.isDirty()) { // text model dirty -> 3wm is dirty return true; } if (!this._ignoreUnhandledConflictsForDirtyState) { - // unhandled conflicts -> 3wm is dirty UNLESS we explicitly set this input - // to ignore unhandled conflicts for the dirty-state. This happens only - // after confirming to ignore unhandled changes + // unhandled conflicts -> 3wm asks to confirm UNLESS we explicitly set this input + // to ignore unhandled conflicts. This happens only after confirming to ignore unhandled changes return Boolean(this._model && this._model.hasUnhandledConflicts.get()); } return false; } - override async confirm(editors?: ReadonlyArray): Promise { + async confirm(editors?: ReadonlyArray): Promise { const inputs: MergeEditorInput[] = [this]; if (editors) { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts index b80d8fae41c..f5e73947655 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts @@ -9,7 +9,7 @@ import { dispose, toDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { EditorInputCapabilities, IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput'; import { ITerminalInstance, ITerminalInstanceService, terminalEditorId } from 'vs/workbench/contrib/terminal/browser/terminal'; import { getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -23,7 +23,7 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin import { ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { Emitter } from 'vs/base/common/event'; -export class TerminalEditorInput extends EditorInput { +export class TerminalEditorInput extends EditorInput implements IEditorCloseHandler { protected readonly _onDidRequestAttach = this._register(new Emitter()); readonly onDidRequestAttach = this._onDidRequestAttach.event; @@ -106,7 +106,13 @@ export class TerminalEditorInput extends EditorInput { return false; } - override async confirm(terminals?: ReadonlyArray): Promise { + override readonly closeHandler = this; + + showConfirm(): boolean { + return this.isDirty(); + } + + async confirm(terminals?: ReadonlyArray): Promise { const { choice } = await this._dialogService.show( Severity.Warning, localize('confirmDirtyTerminal.message', "Do you want to terminate running processes?"), -- cgit v1.2.3 From 0f1adf394001248c1cc251f7a2290a8b5581b906 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 8 Jul 2022 13:05:06 +0200 Subject: only reveal first conflict when not having previous view state (#154482) fixes https://github.com/microsoft/vscode/issues/153962 --- .../contrib/mergeEditor/browser/view/mergeEditor.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index ff44bc01163..abd5efbe468 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -307,16 +307,17 @@ export class MergeEditor extends AbstractTextEditor { this._ctxBaseResourceScheme.set(model.base.uri.scheme); const viewState = this.loadEditorViewState(input, context); - this._applyViewState(viewState); - - this._sessionDisposables.add(thenIfNotDisposed(model.onInitialized, () => { - const firstConflict = model.modifiedBaseRanges.get().find(r => r.isConflicting); - if (!firstConflict) { - return; - } - - this.input1View.editor.revealLineInCenter(firstConflict.input1Range.startLineNumber); - })); + if (viewState) { + this._applyViewState(viewState); + } else { + this._sessionDisposables.add(thenIfNotDisposed(model.onInitialized, () => { + const firstConflict = model.modifiedBaseRanges.get().find(r => r.isConflicting); + if (!firstConflict) { + return; + } + this.input1View.editor.revealLineInCenter(firstConflict.input1Range.startLineNumber); + })); + } this._sessionDisposables.add(autorunWithStore((reader, store) => { -- cgit v1.2.3 From 1c51e8fb91269fb765949794f3cc067349ed89d6 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 8 Jul 2022 13:20:44 +0200 Subject: Add a specialized unhandled conflicts close handler (#154471) This is set dynamically whenever unhandled conflicts are detected. It unsets itself when merging is done so that the normal save-close-handler comes to place https://github.com/microsoft/vscode/issues/152841 --- .../mergeEditor/browser/mergeEditorInput.ts | 110 +++++++++------------ 1 file changed, 46 insertions(+), 64 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 377dc53be55..4f29941dc13 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -19,9 +19,8 @@ import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/text import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { assertType } from 'vs/base/common/types'; +import { autorun } from 'vs/base/common/observable'; export class MergeEditorInputData { constructor( @@ -32,13 +31,14 @@ export class MergeEditorInputData { ) { } } -export class MergeEditorInput extends AbstractTextResourceEditorInput implements ILanguageSupport, IEditorCloseHandler { +export class MergeEditorInput extends AbstractTextResourceEditorInput implements ILanguageSupport { static readonly ID = 'mergeEditor.Input'; private _model?: MergeEditorModel; private _outTextModel?: ITextFileEditorModel; - private _ignoreUnhandledConflictsForDirtyState?: true; + + override closeHandler: MergeEditorCloseHandler | undefined; constructor( public readonly base: URI, @@ -47,8 +47,6 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements public readonly result: URI, @IInstantiationService private readonly _instaService: IInstantiationService, @ITextModelService private readonly _textModelService: ITextModelService, - @IDialogService private readonly _dialogService: IDialogService, - @IFilesConfigurationService private readonly _filesConfigurationService: IFilesConfigurationService, @IEditorService editorService: IEditorService, @ITextFileService textFileService: ITextFileService, @ILabelService labelService: ILabelService, @@ -117,6 +115,13 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements }, ); + // set/unset the closeHandler whenever unhandled conflicts are detected + const closeHandler = this._instaService.createInstance(MergeEditorCloseHandler, this._model); + this._store.add(autorun('closeHandler', reader => { + const value = this._model!.hasUnhandledConflicts.read(reader); + this.closeHandler = value ? closeHandler : undefined; + })); + await this._model.onInitialized; this._store.add(this._model); @@ -126,7 +131,6 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements this._store.add(result); } - this._ignoreUnhandledConflictsForDirtyState = undefined; return this._model; } @@ -146,67 +150,54 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements return Boolean(this._outTextModel?.isDirty()); } - override readonly closeHandler = this; + setLanguageId(languageId: string, _setExplicitly?: boolean): void { + this._model?.setLanguageId(languageId); + } + + // implement get/set languageId + // implement get/set encoding +} + +class MergeEditorCloseHandler implements IEditorCloseHandler { + + private _ignoreUnhandledConflicts: boolean = false; + + constructor( + private readonly _model: MergeEditorModel, + @IDialogService private readonly _dialogService: IDialogService, + ) { } showConfirm(): boolean { - if (this.isDirty()) { - // text model dirty -> 3wm is dirty - return true; - } - if (!this._ignoreUnhandledConflictsForDirtyState) { - // unhandled conflicts -> 3wm asks to confirm UNLESS we explicitly set this input - // to ignore unhandled conflicts. This happens only after confirming to ignore unhandled changes - return Boolean(this._model && this._model.hasUnhandledConflicts.get()); - } - return false; + // unhandled conflicts -> 3wm asks to confirm UNLESS we explicitly set this input + // to ignore unhandled conflicts. This happens only after confirming to ignore unhandled changes + return !this._ignoreUnhandledConflicts && this._model.hasUnhandledConflicts.get(); } - async confirm(editors?: ReadonlyArray): Promise { + async confirm(editors?: readonly IEditorIdentifier[] | undefined): Promise { - const inputs: MergeEditorInput[] = [this]; - if (editors) { - for (const { editor } of editors) { - if (editor instanceof MergeEditorInput) { - inputs.push(editor); - } - } - } + const handler: MergeEditorCloseHandler[] = [this]; + editors?.forEach(candidate => candidate.editor.closeHandler instanceof MergeEditorCloseHandler && handler.push(candidate.editor.closeHandler)); - const inputsWithUnhandledConflicts = inputs + const inputsWithUnhandledConflicts = handler .filter(input => input._model && input._model.hasUnhandledConflicts.get()); if (inputsWithUnhandledConflicts.length === 0) { + // shouldn't happen return ConfirmResult.SAVE; } - const actions: string[] = []; + const actions: string[] = [ + localize('unhandledConflicts.ignore', "Continue with Conflicts"), + localize('unhandledConflicts.discard', "Discard Merge Changes"), + localize('unhandledConflicts.cancel', "Cancel"), + ]; const options = { - cancelId: 0, - detail: inputs.length > 1 - ? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', inputs.length) + cancelId: 2, + detail: handler.length > 1 + ? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', handler.length) : localize('unhandledConflicts.detail1', 'Merge conflicts in this editor will remain unhandled.') }; - const isAnyAutoSave = this._filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF; - if (!isAnyAutoSave) { - // manual-save: FYI and discard - actions.push( - localize('unhandledConflicts.manualSaveIgnore', "Save and Continue with Conflicts"), // 0 - localize('unhandledConflicts.discard', "Discard Merge Changes"), // 1 - localize('unhandledConflicts.manualSaveNoSave', "Don't Save"), // 2 - ); - - } else { - // auto-save: only FYI - actions.push( - localize('unhandledConflicts.ignore', "Continue with Conflicts"), // 0 - localize('unhandledConflicts.discard', "Discard Merge Changes"), // 1 - ); - } - - actions.push(localize('unhandledConflicts.cancel', "Cancel")); - options.cancelId = actions.length - 1; - const { choice } = await this._dialogService.show( Severity.Info, localize('unhandledConflicts.msg', 'Do you want to continue with unhandled conflicts?'), // 1 @@ -221,8 +212,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements // save or revert: in both cases we tell the inputs to ignore unhandled conflicts // for the dirty state computation. - for (const input of inputs) { - input._ignoreUnhandledConflictsForDirtyState = true; + for (const input of handler) { + input._ignoreUnhandledConflicts = true; } if (choice === 0) { @@ -231,7 +222,7 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements } else if (choice === 1) { // discard: undo all changes and save original (pre-merge) state - for (const input of inputs) { + for (const input of handler) { input._discardMergeChanges(); } return ConfirmResult.SAVE; @@ -243,8 +234,6 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements } private _discardMergeChanges(): void { - assertType(this._model !== undefined); - const chunks: string[] = []; while (true) { const chunk = this._model.resultSnapshot.read(); @@ -255,11 +244,4 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements } this._model.result.setValue(chunks.join()); } - - setLanguageId(languageId: string, _setExplicitly?: boolean): void { - this._model?.setLanguageId(languageId); - } - - // implement get/set languageId - // implement get/set encoding } -- cgit v1.2.3 From c6736e14839a020b2a4fc656b6fe672471eaf17e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Jul 2022 04:45:16 -0700 Subject: Remove ability for terminal editors to be dirty Fixes #154496 --- .../terminal/browser/terminalEditorInput.ts | 31 +++++----------------- 1 file changed, 6 insertions(+), 25 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts index f5e73947655..e851f6a8cf2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts @@ -25,19 +25,20 @@ import { Emitter } from 'vs/base/common/event'; export class TerminalEditorInput extends EditorInput implements IEditorCloseHandler { - protected readonly _onDidRequestAttach = this._register(new Emitter()); - readonly onDidRequestAttach = this._onDidRequestAttach.event; - static readonly ID = 'workbench.editors.terminal'; + override readonly closeHandler = this; + private _isDetached = false; private _isShuttingDown = false; private _isReverted = false; private _copyLaunchConfig?: IShellLaunchConfig; private _terminalEditorFocusContextKey: IContextKey; - private _group: IEditorGroup | undefined; + protected readonly _onDidRequestAttach = this._register(new Emitter()); + readonly onDidRequestAttach = this._onDidRequestAttach.event; + setGroup(group: IEditorGroup | undefined) { this._group = group; } @@ -64,13 +65,6 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand } this._terminalInstance = instance; this._setupInstanceListeners(); - - // Refresh dirty state when the confirm on kill setting is changed - this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(TerminalSettingId.ConfirmOnKill)) { - this._onDidChangeDirty.fire(); - } - }); } override copy(): EditorInput { @@ -95,7 +89,7 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand return this._isDetached ? undefined : this._terminalInstance; } - override isDirty(): boolean { + showConfirm(): boolean { if (this._isReverted) { return false; } @@ -106,12 +100,6 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand return false; } - override readonly closeHandler = this; - - showConfirm(): boolean { - return this.isDirty(); - } - async confirm(terminals?: ReadonlyArray): Promise { const { choice } = await this._dialogService.show( Severity.Warning, @@ -154,12 +142,6 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand this._terminalEditorFocusContextKey = TerminalContextKeys.editorFocus.bindTo(_contextKeyService); - // Refresh dirty state when the confirm on kill setting is changed - this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(TerminalSettingId.ConfirmOnKill)) { - this._onDidChangeDirty.fire(); - } - }); if (_terminalInstance) { this._setupInstanceListeners(); } @@ -184,7 +166,6 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand instance.onIconChanged(() => this._onDidChangeLabel.fire()), instance.onDidFocus(() => this._terminalEditorFocusContextKey.set(true)), instance.onDidBlur(() => this._terminalEditorFocusContextKey.reset()), - instance.onDidChangeHasChildProcesses(() => this._onDidChangeDirty.fire()), instance.statusList.onDidChangePrimaryStatus(() => this._onDidChangeLabel.fire()) ]; -- cgit v1.2.3 From 1eb2480d91650f82b3b1da0b05d382a01b2d38e2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Jul 2022 07:23:00 -0700 Subject: Don't write initialText new line when echo is false Fixes #152645 --- .../contrib/tasks/browser/terminalTaskSystem.ts | 10 +++++-- .../contrib/terminal/browser/terminalInstance.ts | 32 ++++++++++++++++++---- 2 files changed, 34 insertions(+), 8 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 754a84163c1..7cfab363b6d 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -1157,7 +1157,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { }, 'Executing task: {0}', commandLine), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence; } } else { - shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence; + shellLaunchConfig.initialText = { + text: this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence, + trailingNewLine: false + }; } } else { const commandExecutable = (task.command.runtime !== RuntimeType.CustomExecution) ? CommandString.value(command) : undefined; @@ -1197,7 +1200,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { }, 'Executing task: {0}', `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence; } } else { - shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence; + shellLaunchConfig.initialText = { + text: this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence, + trailingNewLine: false + }; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 8d398d7a8be..e4187f6ab3c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -698,7 +698,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Write initial text, deferring onLineFeed listener when applicable to avoid firing // onLineData events containing initialText if (this._shellLaunchConfig.initialText) { - this.xterm.raw.writeln(this._shellLaunchConfig.initialText, () => { + this._writeInitialText(this.xterm, () => { lineDataEventAddon.onLineData(e => this._onLineData.fire(e)); }); } else { @@ -1821,29 +1821,49 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } + private _writeInitialText(xterm: XtermTerminal, callback?: () => void): void { + if (!this._shellLaunchConfig.initialText) { + callback?.(); + return; + } + const text = typeof this._shellLaunchConfig.initialText === 'string' + ? this._shellLaunchConfig.initialText + : this._shellLaunchConfig.initialText?.text; + if (typeof this._shellLaunchConfig.initialText === 'string') { + xterm.raw.writeln(text, callback); + } else { + if (this._shellLaunchConfig.initialText.trailingNewLine) { + xterm.raw.writeln(text, callback); + } else { + xterm.raw.write(text, callback); + } + } + } + async reuseTerminal(shell: IShellLaunchConfig, reset: boolean = false): Promise { // Unsubscribe any key listener we may have. this._pressAnyKeyToCloseListener?.dispose(); this._pressAnyKeyToCloseListener = undefined; - if (this.xterm) { + const xterm = this.xterm; + if (xterm) { if (!reset) { // Ensure new processes' output starts at start of new line - await new Promise(r => this.xterm!.raw.write('\n\x1b[G', r)); + await new Promise(r => xterm.raw.write('\n\x1b[G', r)); } // Print initialText if specified if (shell.initialText) { - await new Promise(r => this.xterm!.raw.writeln(shell.initialText!, r)); + await new Promise(r => this._writeInitialText(xterm, r)); } // Clean up waitOnExit state if (this._isExiting && this._shellLaunchConfig.waitOnExit) { - this.xterm.raw.options.disableStdin = false; + xterm.raw.options.disableStdin = false; this._isExiting = false; } if (reset) { - this.xterm.clearDecorations(); + xterm.clearDecorations(); } } -- cgit v1.2.3 From 4ccbed762465c3fc7cabb2837652aa35c289ebb6 Mon Sep 17 00:00:00 2001 From: Jean Pierre Date: Fri, 8 Jul 2022 10:10:04 -0500 Subject: Update src/vs/workbench/contrib/terminal/browser/terminalInstance.ts Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 5a98560dae0..c1bbe018ea2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1368,7 +1368,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._pressAnyKeyToCloseListener = undefined; } - if (typeof this._exitReason === 'undefined') { + if (this._exitReason === undefined) { this._exitReason = reason ?? TerminalExitReason.Unknown; } -- cgit v1.2.3 From 8f8a5740bf1927fb12624d47811db803db5a505c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Jul 2022 10:37:00 -0700 Subject: Run recent command: Collapse $HOME into ~ Fixes #153109 --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index e4187f6ab3c..18e96129340 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -47,7 +47,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { TerminalCapabilityStoreMultiplexer } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, PosixShellType, ProcessPropertyType, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal'; -import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; +import { escapeNonWindowsPath, getTildePath } from 'vs/platform/terminal/common/terminalEnvironment'; import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -851,7 +851,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (label.length === 0 || commandMap.has(label)) { continue; } - let description = `${entry.cwd}`; + let description = getTildePath(entry.cwd, this._userHome, this._processManager?.os === OperatingSystem.Windows ? '\\' : '/'); if (entry.exitCode) { // Since you cannot get the last command's exit code on pwsh, just whether it failed // or not, -1 is treated specially as simply failed -- cgit v1.2.3 From aba83292959ac69db22cf2e7b3ceb32de2b766eb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Jul 2022 10:44:54 -0700 Subject: Add tests, improve name --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 18e96129340..408e3b14ce5 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -47,7 +47,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { TerminalCapabilityStoreMultiplexer } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, PosixShellType, ProcessPropertyType, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal'; -import { escapeNonWindowsPath, getTildePath } from 'vs/platform/terminal/common/terminalEnvironment'; +import { escapeNonWindowsPath, collapseTildePath } from 'vs/platform/terminal/common/terminalEnvironment'; import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -851,7 +851,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (label.length === 0 || commandMap.has(label)) { continue; } - let description = getTildePath(entry.cwd, this._userHome, this._processManager?.os === OperatingSystem.Windows ? '\\' : '/'); + let description = collapseTildePath(entry.cwd, this._userHome, this._processManager?.os === OperatingSystem.Windows ? '\\' : '/'); if (entry.exitCode) { // Since you cannot get the last command's exit code on pwsh, just whether it failed // or not, -1 is treated specially as simply failed -- cgit v1.2.3 From e6a60c37d705a25d3875f2e89c5ad551fd846cd9 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Fri, 8 Jul 2022 11:56:58 -0700 Subject: Use `idToken` for edit sessions (#154534) --- .../contrib/editSessions/browser/editSessionsWorkbenchService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts index af25b5ee15a..24962e02c6a 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts @@ -160,7 +160,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes const existing = await this.getExistingSession(); if (existing !== undefined) { this.logService.trace(`Found existing authentication session with ID ${existingSessionId}`); - this.#authenticationInfo = { sessionId: existing.session.id, token: existing.session.accessToken, providerId: existing.session.providerId }; + this.#authenticationInfo = { sessionId: existing.session.id, token: existing.session.idToken ?? existing.session.accessToken, providerId: existing.session.providerId }; this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId); return true; } @@ -169,7 +169,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes // Ask the user to pick a preferred account const session = await this.getAccountPreference(); if (session !== undefined) { - this.#authenticationInfo = { sessionId: session.id, token: session.accessToken, providerId: session.providerId }; + this.#authenticationInfo = { sessionId: session.id, token: session.idToken ?? session.accessToken, providerId: session.providerId }; this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId); this.existingSessionId = session.id; this.logService.trace(`Saving authentication session preference for ID ${session.id}.`); -- cgit v1.2.3 From a658bc96881e9b2438d6cbd8f49d5d5a2a61a10b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Jul 2022 12:47:49 -0700 Subject: Add inTerminalRunCommandPicker context key Fixes #154306 --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 7 ++++++- .../workbench/contrib/terminal/browser/terminalInstanceService.ts | 3 +++ src/vs/workbench/contrib/terminal/common/terminalContextKey.ts | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index e4187f6ab3c..b8ede51f94c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -355,6 +355,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private readonly _terminalHasFixedWidth: IContextKey, private readonly _terminalShellTypeContextKey: IContextKey, private readonly _terminalAltBufferActiveContextKey: IContextKey, + private readonly _terminalInRunCommandPicker: IContextKey, private readonly _configHelper: TerminalConfigHelper, private _shellLaunchConfig: IShellLaunchConfig, resource: URI | undefined, @@ -1024,7 +1025,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } return new Promise(r => { quickPick.show(); - quickPick.onDidHide(() => r()); + this._terminalInRunCommandPicker.set(true); + quickPick.onDidHide(() => { + this._terminalInRunCommandPicker.set(false); + r(); + }); }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index c9da9557b27..a7732ab7c62 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -23,6 +23,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst private _terminalHasFixedWidth: IContextKey; private _terminalShellTypeContextKey: IContextKey; private _terminalAltBufferActiveContextKey: IContextKey; + private _terminalInRunCommandPicker: IContextKey; private _configHelper: TerminalConfigHelper; private readonly _onDidCreateInstance = new Emitter(); @@ -37,6 +38,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst this._terminalHasFixedWidth = TerminalContextKeys.terminalHasFixedWidth.bindTo(this._contextKeyService); this._terminalShellTypeContextKey = TerminalContextKeys.shellType.bindTo(this._contextKeyService); this._terminalAltBufferActiveContextKey = TerminalContextKeys.altBufferActive.bindTo(this._contextKeyService); + this._terminalInRunCommandPicker = TerminalContextKeys.inTerminalRunCommandPicker.bindTo(this._contextKeyService); this._configHelper = _instantiationService.createInstance(TerminalConfigHelper); } @@ -49,6 +51,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst this._terminalHasFixedWidth, this._terminalShellTypeContextKey, this._terminalAltBufferActiveContextKey, + this._terminalInRunCommandPicker, this._configHelper, shellLaunchConfig, resource diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 72f1792fc71..1d292d45536 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -31,6 +31,7 @@ export const enum TerminalContextKeyStrings { TabsSingularSelection = 'terminalTabsSingularSelection', SplitTerminal = 'terminalSplitTerminal', ShellType = 'terminalShellType', + InTerminalRunCommandPicker = 'inTerminalRunCommandPicker', } export namespace TerminalContextKeys { @@ -119,4 +120,7 @@ export namespace TerminalContextKeys { /** Whether the focused tab's terminal is a split terminal. */ export const splitTerminal = new RawContextKey(TerminalContextKeyStrings.SplitTerminal, false, localize('isSplitTerminalContextKey', "Whether the focused tab's terminal is a split terminal.")); + + /** Whether the terminal run command picker is currently open. */ + export const inTerminalRunCommandPicker = new RawContextKey(TerminalContextKeyStrings.InTerminalRunCommandPicker, false, localize('inTerminalRunCommandPickerContextKey', "Whether the terminal run command picker is currently open.")); } -- cgit v1.2.3 From 70bcfa0172f7f81fad74845a4c5a35e6e2c3b528 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Jul 2022 13:04:28 -0700 Subject: Fix incorrect relaunch when exit code is 0 Part of #154421 --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index b8ede51f94c..91857de9b20 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1703,7 +1703,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const parsedExitResult = parseExitResult(exitCodeOrError, this.shellLaunchConfig, this._processManager.processState, this._initialCwd); - if (this._usedShellIntegrationInjection && (this._processManager.processState === ProcessState.KilledDuringLaunch || this._processManager.processState === ProcessState.KilledByProcess)) { + if (this._usedShellIntegrationInjection && (this._processManager.processState === ProcessState.KilledDuringLaunch || this._processManager.processState === ProcessState.KilledByProcess) && parsedExitResult?.code !== 0) { this._relaunchWithShellIntegrationDisabled(parsedExitResult?.message); this._onExit.fire(exitCodeOrError); return; -- cgit v1.2.3 From b710cb58ece5528ee0cad367bd4a47a8af5f46fe Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Jul 2022 13:06:26 -0700 Subject: Don't relaunch shell integration terminals when killed by process The failures we care about should be covered by KilledDuringLaunch Fixes #154421 --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 91857de9b20..c9fb84a7d0a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1703,7 +1703,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const parsedExitResult = parseExitResult(exitCodeOrError, this.shellLaunchConfig, this._processManager.processState, this._initialCwd); - if (this._usedShellIntegrationInjection && (this._processManager.processState === ProcessState.KilledDuringLaunch || this._processManager.processState === ProcessState.KilledByProcess) && parsedExitResult?.code !== 0) { + if (this._usedShellIntegrationInjection && this._processManager.processState === ProcessState.KilledDuringLaunch && parsedExitResult?.code !== 0) { this._relaunchWithShellIntegrationDisabled(parsedExitResult?.message); this._onExit.fire(exitCodeOrError); return; -- cgit v1.2.3 From 3972e8e8892369f57f2549152fb7c4c161dbcb23 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Jul 2022 13:50:09 -0700 Subject: Set status once, prevent preexec recursing Fixes #150241 --- .../browser/media/shellIntegration-bash.sh | 37 ++++++++++++---------- 1 file changed, 20 insertions(+), 17 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index 88498fbfb9a..d6a41229e4a 100755 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -110,23 +110,22 @@ __vsc_precmd() { } __vsc_preexec() { - if [ "$__vsc_in_command_execution" = "0" ]; then - __vsc_initialized=1 - __vsc_in_command_execution="1" - if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then - __vsc_current_command=$BASH_COMMAND - else - __vsc_current_command="" - fi - __vsc_command_output_start + __vsc_initialized=1 + if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then + __vsc_current_command=$BASH_COMMAND + else + __vsc_current_command="" fi + __vsc_command_output_start } # Debug trapping/preexec inspired by starship (ISC) if [[ -n "${bash_preexec_imported:-}" ]]; then __vsc_preexec_only() { - __vsc_status="$?" - __vsc_preexec + if [ "$__vsc_in_command_execution" = "0" ]; then + __vsc_in_command_execution="1" + __vsc_preexec + fi } precmd_functions+=(__vsc_prompt_cmd) preexec_functions+=(__vsc_preexec_only) @@ -134,15 +133,19 @@ else __vsc_dbg_trap="$(trap -p DEBUG | cut -d' ' -f3 | tr -d \')" if [[ -z "$__vsc_dbg_trap" ]]; then __vsc_preexec_only() { - __vsc_status="$?" - __vsc_preexec + if [ "$__vsc_in_command_execution" = "0" ]; then + __vsc_in_command_execution="1" + __vsc_preexec + fi } trap '__vsc_preexec_only "$_"' DEBUG elif [[ "$__vsc_dbg_trap" != '__vsc_preexec "$_"' && "$__vsc_dbg_trap" != '__vsc_preexec_all "$_"' ]]; then __vsc_preexec_all() { - __vsc_status="$?" - builtin eval ${__vsc_dbg_trap} - __vsc_preexec + if [ "$__vsc_in_command_execution" = "0" ]; then + __vsc_in_command_execution="1" + builtin eval ${__vsc_dbg_trap} + __vsc_preexec + fi } trap '__vsc_preexec_all "$_"' DEBUG fi @@ -151,6 +154,7 @@ fi __vsc_update_prompt __vsc_prompt_cmd_original() { + __vsc_status="$?" if [[ ${IFS+set} ]]; then __vsc_original_ifs="$IFS" fi @@ -174,7 +178,6 @@ __vsc_prompt_cmd_original() { } __vsc_prompt_cmd() { - __vsc_status="$?" __vsc_precmd } -- cgit v1.2.3 From 3879c72594b54fdbe67f788c6d7c4fd61ff6abc7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Jul 2022 13:51:40 -0700 Subject: Also set __vsc_status when there are no traps --- src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh | 1 + 1 file changed, 1 insertion(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index d6a41229e4a..a2f4afa1400 100755 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -178,6 +178,7 @@ __vsc_prompt_cmd_original() { } __vsc_prompt_cmd() { + __vsc_status="$?" __vsc_precmd } -- cgit v1.2.3 From 099759c8a7605a7407b8a1fffce0bacf0a7757c8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Jul 2022 14:23:02 -0700 Subject: Apply bracketed paste mode on text send to the terminal Fixes #153592 --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index b8ede51f94c..25387b4b62e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1460,9 +1460,15 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } async sendText(text: string, addNewLine: boolean): Promise { + // Apply bracketed paste sequences if the terminal has the mode enabled, this will prevent + // the text from triggering keybindings https://github.com/microsoft/vscode/issues/153592 + if (this.xterm?.raw.modes.bracketedPasteMode) { + text = `\x1b[200~${text}\x1b[201~`; + } + // Normalize line endings to 'enter' press. text = text.replace(/\r?\n/g, '\r'); - if (addNewLine && text.substr(text.length - 1) !== '\r') { + if (addNewLine && text.at(-1) !== '\r') { text += '\r'; } -- cgit v1.2.3 From 68ced4032c71d073fb5d6182f0800303eea16149 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 8 Jul 2022 14:29:31 -0700 Subject: Don't use at() yet --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 25387b4b62e..cb479a3366e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1468,7 +1468,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Normalize line endings to 'enter' press. text = text.replace(/\r?\n/g, '\r'); - if (addNewLine && text.at(-1) !== '\r') { + if (addNewLine && text[text.length - 1] !== '\r') { text += '\r'; } -- cgit v1.2.3 From d287d59d290be69c23466e37ece1facc66682996 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 8 Jul 2022 15:09:05 -0700 Subject: testing: persist empty history filter (#154573) Fixes #150120 --- .../contrib/testing/browser/testingExplorerFilter.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts index 52d57770b21..e9d136fdc9f 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts @@ -35,7 +35,7 @@ const testFilterDescriptions: { [K in TestFilterTerm]: string } = { export class TestingExplorerFilter extends BaseActionViewItem { private input!: SuggestEnabledInputWithHistory; private wrapper!: HTMLDivElement; - private readonly history: StoredValue = this.instantiationService.createInstance(StoredValue, { + private readonly history: StoredValue<{ values: string[]; lastValue: string } | string[]> = this.instantiationService.createInstance(StoredValue, { key: 'testing.filterHistory2', scope: StorageScope.WORKSPACE, target: StorageTarget.USER @@ -65,9 +65,12 @@ export class TestingExplorerFilter extends BaseActionViewItem { const wrapper = this.wrapper = dom.$('.testing-filter-wrapper'); container.appendChild(wrapper); - const history = this.history.get([]); - if (history.length) { - this.state.setText(history[history.length - 1]); + let history = this.history.get({ lastValue: '', values: [] }); + if (history instanceof Array) { + history = { lastValue: '', values: history }; + } + if (history.lastValue) { + this.state.setText(history.lastValue); } const input = this.input = this._register(this.instantiationService.createInstance(ContextScopedSuggestEnabledInputWithHistory, { @@ -94,7 +97,7 @@ export class TestingExplorerFilter extends BaseActionViewItem { value: this.state.text.value, placeholderText: localize('testExplorerFilter', "Filter (e.g. text, !exclude, @tag)"), }, - history + history: history.values })); this._register(attachSuggestEnabledInputBoxStyler(input, this.themeService)); @@ -145,12 +148,7 @@ export class TestingExplorerFilter extends BaseActionViewItem { * Persists changes to the input history. */ public saveState() { - const history = this.input.getHistory(); - if (history.length) { - this.history.store(history); - } else { - this.history.delete(); - } + this.history.store({ lastValue: this.input.getValue(), values: this.input.getHistory() }); } /** -- cgit v1.2.3 From 2a4e52e3c0778c6558e3f385add9de880a12325d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sat, 9 Jul 2022 00:23:14 +0200 Subject: Enable profiles in web --- src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 44aa262be18..d86d86f4cf8 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -6,7 +6,6 @@ import { Codicon } from 'vs/base/common/codicons'; import { Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { isWeb } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -53,7 +52,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } private registerConfiguration(): void { - if (!isWeb && this.productService.quality !== 'stable') { + if (this.productService.quality !== 'stable') { Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ ...workbenchConfigurationNodeBase, 'properties': { -- cgit v1.2.3 From 1b6ce581ebfbbfb4572a4433f6450c78d2e1a052 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Fri, 8 Jul 2022 16:53:36 -0700 Subject: Debt - Add execution id to edit session request headers (#154575) Add execution id to edit session request headers --- .../browser/editSessions.contribution.ts | 32 ++++++++++------------ .../browser/editSessionsWorkbenchService.ts | 10 ++++--- .../editSessions/test/browser/editSessions.test.ts | 4 +-- 3 files changed, 23 insertions(+), 23 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index deb0cb486bc..12f620df779 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -85,7 +85,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo super(); if (this.environmentService.editSessionId !== undefined) { - void this.applyEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined); + void this.resumeEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined); } this.configurationService.onDidChangeConfiguration((e) => { @@ -132,7 +132,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo this.registerContinueEditSessionAction(); - this.registerApplyLatestEditSessionAction(); + this.registerResumeLatestEditSessionAction(); this.registerStoreLatestEditSessionAction(); this.registerContinueInLocalFolderAction(); @@ -171,9 +171,9 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo })); } - private registerApplyLatestEditSessionAction(): void { + private registerResumeLatestEditSessionAction(): void { const that = this; - this._register(registerAction2(class ApplyLatestEditSessionAction extends Action2 { + this._register(registerAction2(class ResumeLatestEditSessionAction extends Action2 { constructor() { super({ id: 'workbench.experimental.editSessions.actions.resumeLatest', @@ -186,8 +186,8 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo async run(accessor: ServicesAccessor): Promise { await that.progressService.withProgress({ location: ProgressLocation.Notification, - title: localize('applying edit session', 'Applying edit session...') - }, async () => await that.applyEditSession()); + title: localize('resuming edit session', 'Resuming edit session...') + }, async () => await that.resumeEditSession()); } })); } @@ -213,26 +213,24 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo })); } - async applyEditSession(ref?: string): Promise { - if (ref !== undefined) { - this.logService.info(`Applying edit session with ref ${ref}.`); - } + async resumeEditSession(ref?: string): Promise { + this.logService.info(ref !== undefined ? `Resuming edit session with ref ${ref}...` : 'Resuming edit session...'); const data = await this.editSessionsWorkbenchService.read(ref); if (!data) { if (ref === undefined) { - this.notificationService.info(localize('no edit session', 'There are no edit sessions to apply.')); + this.notificationService.info(localize('no edit session', 'There are no edit sessions to resume.')); } else { - this.notificationService.warn(localize('no edit session content for ref', 'Could not apply edit session contents for ID {0}.', ref)); + this.notificationService.warn(localize('no edit session content for ref', 'Could not resume edit session contents for ID {0}.', ref)); } - this.logService.info(`Aborting applying edit session as no edit session content is available to be applied from ref ${ref}.`); + this.logService.info(`Aborting resuming edit session as no edit session content is available to be applied from ref ${ref}.`); return; } const editSession = data.editSession; ref = data.ref; if (editSession.version > EditSessionSchemaVersion) { - this.notificationService.error(localize('client too old', "Please upgrade to a newer version of {0} to apply this edit session.", this.productService.nameLong)); + this.notificationService.error(localize('client too old', "Please upgrade to a newer version of {0} to resume this edit session.", this.productService.nameLong)); return; } @@ -266,7 +264,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo if (hasLocalUncommittedChanges) { // TODO@joyceerhl Provide the option to diff files which would be overwritten by edit session contents const result = await this.dialogService.confirm({ - message: localize('apply edit session warning', 'Applying your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'), + message: localize('resume edit session warning', 'Resuming your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'), type: 'warning', title: EDIT_SESSION_SYNC_CATEGORY.value }); @@ -287,8 +285,8 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo await this.editSessionsWorkbenchService.delete(ref); this.logService.info(`Deleted edit session with ref ${ref}.`); } catch (ex) { - this.logService.error('Failed to apply edit session, reason: ', (ex as Error).toString()); - this.notificationService.error(localize('apply failed', "Failed to apply your edit session.")); + this.logService.error('Failed to resume edit session, reason: ', (ex as Error).toString()); + this.notificationService.error(localize('resume failed', "Failed to resume your edit session.")); } } diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts index 24962e02c6a..615f7b0f669 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts @@ -14,11 +14,12 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IRequestService } from 'vs/platform/request/common/request'; import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync'; +import { createSyncHeaders, IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, IEditSessionsWorkbenchService, EDIT_SESSIONS_SIGNED_IN_KEY, IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions'; +import { generateUuid } from 'vs/base/common/uuid'; type ExistingSession = IQuickPickItem & { session: AuthenticationSession & { providerId: string } }; type AuthenticationProviderOption = IQuickPickItem & { provider: IAuthenticationProvider }; @@ -73,7 +74,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes throw new Error('Please sign in to store your edit session.'); } - return this.storeClient!.write('editSessions', JSON.stringify(editSession), null); + return this.storeClient!.write('editSessions', JSON.stringify(editSession), null, createSyncHeaders(generateUuid())); } /** @@ -89,11 +90,12 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes } let content: string | undefined | null; + const headers = createSyncHeaders(generateUuid()); try { if (ref !== undefined) { - content = await this.storeClient?.resolveContent('editSessions', ref); + content = await this.storeClient?.resolveContent('editSessions', ref, headers); } else { - const result = await this.storeClient?.read('editSessions', null); + const result = await this.storeClient?.read('editSessions', null, headers); content = result?.content; ref = result?.ref; } diff --git a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts index b7caca6f077..07917f285e5 100644 --- a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts +++ b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts @@ -112,8 +112,8 @@ suite('Edit session sync', () => { // Create root folder await fileService.createFolder(folderUri); - // Apply edit session - await editSessionsContribution.applyEditSession(); + // Resume edit session + await editSessionsContribution.resumeEditSession(); // Verify edit session was correctly applied assert.equal((await fileService.readFile(fileUri)).value.toString(), fileContents); -- cgit v1.2.3 From d7f814a238ade71303ac2b237e4d676a592b5f06 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Fri, 8 Jul 2022 20:29:50 -0400 Subject: Add keybindings for search editor file filters (#153954) * Add keybindings for search editor file filters * Review cleanup --- .../browser/searchEditor.contribution.ts | 40 ++++++++++++++++++++++ .../contrib/searchEditor/browser/searchEditor.ts | 14 ++++++++ 2 files changed, 54 insertions(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index ed23a5f2632..a75b4184bd2 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -41,6 +41,8 @@ import { Disposable } from 'vs/base/common/lifecycle'; const OpenInEditorCommandId = 'search.action.openInEditor'; const OpenNewEditorToSideCommandId = 'search.action.openNewEditorToSide'; const FocusQueryEditorWidgetCommandId = 'search.action.focusQueryEditorWidget'; +const FocusQueryEditorFilesToIncludeCommandId = 'search.action.focusFilesToInclude'; +const FocusQueryEditorFilesToExcludeCommandId = 'search.action.focusFilesToExclude'; const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive'; const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord'; @@ -374,6 +376,44 @@ registerAction2(class extends Action2 { } }); +registerAction2(class extends Action2 { + constructor() { + super({ + id: FocusQueryEditorFilesToIncludeCommandId, + title: { value: localize('search.action.focusFilesToInclude', "Focus Search Editor Files to Include"), original: 'Focus Search Editor Files to Include' }, + category, + f1: true, + precondition: SearchEditorConstants.InSearchEditor, + }); + } + async run(accessor: ServicesAccessor) { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeEditorPane as SearchEditor).focusFilesToIncludeInput(); + } + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: FocusQueryEditorFilesToExcludeCommandId, + title: { value: localize('search.action.focusFilesToExclude', "Focus Search Editor Files to Exclude"), original: 'Focus Search Editor Files to Exclude' }, + category, + f1: true, + precondition: SearchEditorConstants.InSearchEditor, + }); + } + async run(accessor: ServicesAccessor) { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeEditorPane as SearchEditor).focusFilesToExcludeInput(); + } + } +}); + registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 380f8dbcc8f..54be4156b6e 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -275,6 +275,20 @@ export class SearchEditor extends AbstractTextCodeEditor this.queryEditorWidget.searchInput.focus(); } + focusFilesToIncludeInput() { + if (!this.showingIncludesExcludes) { + this.toggleIncludesExcludes(true); + } + this.inputPatternIncludes.focus(); + } + + focusFilesToExcludeInput() { + if (!this.showingIncludesExcludes) { + this.toggleIncludesExcludes(true); + } + this.inputPatternExcludes.focus(); + } + focusNextInput() { if (this.queryEditorWidget.searchInputHasFocus()) { if (this.showingIncludesExcludes) { -- cgit v1.2.3 From 042e505d3230c3b7dcd7bb0ecdfbff8452fd19fb Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Sat, 9 Jul 2022 17:02:18 -0700 Subject: Allow deleting all edit sessions when signing out (#154579) --- .../browser/editSessionsWorkbenchService.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts index 615f7b0f669..b63b550ab35 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts @@ -19,6 +19,7 @@ import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDat import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, IEditSessionsWorkbenchService, EDIT_SESSIONS_SIGNED_IN_KEY, IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { generateUuid } from 'vs/base/common/uuid'; type ExistingSession = IQuickPickItem & { session: AuthenticationSession & { providerId: string } }; @@ -48,6 +49,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes @IProductService private readonly productService: IProductService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IRequestService private readonly requestService: IRequestService, + @IDialogService private readonly dialogService: IDialogService, ) { super(); @@ -352,8 +354,19 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes }); } - run() { - that.clearAuthenticationPreference(); + async run() { + const result = await that.dialogService.confirm({ + type: 'info', + message: localize('sign out of edit sessions clear data prompt', 'Do you want to sign out of edit sessions?'), + checkbox: { label: localize('delete all edit sessions', 'Delete all stored edit sessions from the cloud.') }, + primaryButton: localize('clear data confirm', 'Yes'), + }); + if (result.confirmed) { + if (result.checkboxChecked) { + that.storeClient?.delete('editSessions', null); + } + that.clearAuthenticationPreference(); + } } })); } -- cgit v1.2.3 From 068fd7ffbccc99b26bfb051605251a55d19347cf Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Sun, 10 Jul 2022 17:10:58 +0200 Subject: Problems view table - Fix code column rendering issue (#154518) Fix code column rendering issue --- src/vs/workbench/contrib/markers/browser/markersTable.ts | 9 +++++---- src/vs/workbench/contrib/markers/browser/media/markers.css | 11 ++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/markers/browser/markersTable.ts b/src/vs/workbench/contrib/markers/browser/markersTable.ts index a4938d08d58..4d39773daed 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTable.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTable.ts @@ -135,15 +135,17 @@ class MarkerCodeColumnRenderer implements ITableRenderer .monaco-table-td > .code.code-link > .code-label { +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .code-label, +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .monaco-link { display: none; } -.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-link > .monaco-link { +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-label > .code-label { display: inline; - text-decoration: underline; } -.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .monaco-link { - display: none; +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-link > .monaco-link { + display: inline; + text-decoration: underline; } .markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .file > .file-position { -- cgit v1.2.3 From 2a745e6552dac8324e995b8c73e0c3041f2b1f1f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 10 Jul 2022 20:56:29 +0200 Subject: update icon for settings profiles (#154712) --- src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 44aa262be18..ce8de12db1f 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -15,6 +15,7 @@ import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from ' import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IUserDataProfile, IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -25,6 +26,8 @@ import { IUserDataProfileManagementService, IUserDataProfileService, ManageProfi const CONTEXT_CURRENT_PROFILE = new RawContextKey('currentUserDataProfile', ''); +export const userDataProfilesIcon = registerIcon('settingsProfiles-icon', Codicon.settings, localize('settingsProfilesIcon', 'Icon for Settings Profiles.')); + export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution { private readonly currentProfileContext: IContextKey; @@ -138,7 +141,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements name: PROFILES_CATEGORY, command: 'workbench.profiles.actions.switchProfile', ariaLabel: localize('currentProfile', "Current Settings Profile is {0}", this.userDataProfileService.currentProfile.name), - text: `$(${Codicon.multipleWindows.id}) ${this.userDataProfileService.currentProfile.name!}`, + text: `$(${userDataProfilesIcon.id}) ${this.userDataProfileService.currentProfile.name!}`, tooltip: localize('profileTooltip', "{0}: {1}", PROFILES_CATEGORY, this.userDataProfileService.currentProfile.name), color: themeColorFromId(STATUS_BAR_SETTINGS_PROFILE_FOREGROUND), backgroundColor: themeColorFromId(STATUS_BAR_SETTINGS_PROFILE_BACKGROUND) -- cgit v1.2.3 From e69176aafd282090e18f558c2b2ac9b3d5b606f5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 10 Jul 2022 20:56:54 +0200 Subject: Fix #154180 (#154713) --- src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index ce8de12db1f..d5c7082d236 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -118,7 +118,6 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements id: `workbench.profiles.actions.profileEntry.${profile.id}`, title: profile.name, toggled: ContextKeyExpr.equals(CONTEXT_CURRENT_PROFILE.key, profile.id), - precondition: ContextKeyExpr.notEquals(CONTEXT_CURRENT_PROFILE.key, profile.id), menu: [ { id: ManageProfilesSubMenu, @@ -129,7 +128,9 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements }); } async run(accessor: ServicesAccessor) { - return that.userDataProfileManagementService.switchProfile(profile); + if (that.userDataProfileService.currentProfile.id !== profile.id) { + return that.userDataProfileManagementService.switchProfile(profile); + } } }); } -- cgit v1.2.3 From 5bb939a1a92c8c02fc0770755c40a3076439ec00 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 10 Jul 2022 16:04:29 -0700 Subject: Enable more pwsh keybindings via shell integration Part of #45705 --- .../terminal/browser/media/shellIntegration.ps1 | 16 +++++++++++++ .../terminal/browser/terminal.contribution.ts | 28 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 index aceb31ab780..5d7d4436094 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 @@ -72,3 +72,19 @@ if (Get-Module -Name PSReadLine) { # Set IsWindows property [Console]::Write("`e]633;P;IsWindows=$($IsWindows)`a") + +# Set always on key handlers which map to default VS Code keybindings +function Set-MappedKeyHandler { + param ([string[]] $Chord, [string[]]$Sequence) + $Handler = $(Get-PSReadLineKeyHandler -Chord $Chord) + if ($Handler) { + Set-PSReadLineKeyHandler -Chord $Sequence -Function $Handler.Function + } +} +function Set-MappedKeyHandlers { + Set-MappedKeyHandler -Chord Ctrl+Spacebar -Sequence 'F12,a' + Set-MappedKeyHandler -Chord Alt+Spacebar -Sequence 'F12,b' + Set-MappedKeyHandler -Chord Shift+Enter -Sequence 'F12,c' + Set-MappedKeyHandler -Chord Shift+End -Sequence 'F12,d' +} +Set-MappedKeyHandlers diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index d01b958ae13..8c86caa36af 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -177,6 +177,34 @@ if (isWindows) { }); } +// TODO: This only works when shell integration is enabled - create shell integration enabled for active terminal context key +// Map certain keybindings in pwsh to unused keys which get handled by PSReadLine handlers in the +// shell integration script. This allows keystrokes that cannot be sent via VT sequences to work. +// See https://github.com/microsoft/terminal/issues/879#issuecomment-497775007 +registerSendSequenceKeybinding('\x1b[24~a', { // F12,a -> ctrl+space + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + primary: KeyMod.CtrlCmd | KeyCode.Space, + mac: { primary: KeyMod.WinCtrl | KeyCode.Space } +}); +registerSendSequenceKeybinding('\x1b[24~b', { // F12,b -> alt+space + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + primary: KeyMod.Alt | KeyCode.Space +}); +registerSendSequenceKeybinding('\x1b[24~c', { // F12,c -> shift+enter + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), + primary: KeyMod.Shift | KeyCode.Enter +}); +registerSendSequenceKeybinding('\x1b[24~d', { // F12,d -> shift+end - \x1b[1;2F is supposed to work but it doesn't + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell)), + mac: { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.RightArrow } +}); + +// Always on pwsh keybindings +registerSendSequenceKeybinding('\x1b[1;2H', { // Shift+home + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell)), + mac: { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.LeftArrow } +}); + // send ctrl+c to the iPad when the terminal is focused and ctrl+c is pressed to kill the process (work around for #114009) if (isIOS) { registerSendSequenceKeybinding(String.fromCharCode('C'.charCodeAt(0) - CTRL_LETTER_OFFSET), { // ctrl+c -- cgit v1.2.3 From be10e638bc24b01c973fb4bfd6c29f92bcb83a6d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 10 Jul 2022 16:17:50 -0700 Subject: Ensure shell integration is enables for custom keys Fixes #45705 --- .../terminal/browser/terminal.contribution.ts | 16 ++++++------- .../contrib/terminal/browser/terminalInstance.ts | 28 +++++++++++++++++++--- .../terminal/browser/terminalInstanceService.ts | 3 +++ .../contrib/terminal/common/terminalContextKey.ts | 4 ++++ 4 files changed, 40 insertions(+), 11 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 8c86caa36af..60277418746 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -181,21 +181,21 @@ if (isWindows) { // Map certain keybindings in pwsh to unused keys which get handled by PSReadLine handlers in the // shell integration script. This allows keystrokes that cannot be sent via VT sequences to work. // See https://github.com/microsoft/terminal/issues/879#issuecomment-497775007 -registerSendSequenceKeybinding('\x1b[24~a', { // F12,a -> ctrl+space - when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), +registerSendSequenceKeybinding('\x1b[24~a', { // F12,a -> ctrl+space (MenuComplete) + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), primary: KeyMod.CtrlCmd | KeyCode.Space, mac: { primary: KeyMod.WinCtrl | KeyCode.Space } }); -registerSendSequenceKeybinding('\x1b[24~b', { // F12,b -> alt+space - when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), +registerSendSequenceKeybinding('\x1b[24~b', { // F12,b -> alt+space (SetMark) + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), primary: KeyMod.Alt | KeyCode.Space }); -registerSendSequenceKeybinding('\x1b[24~c', { // F12,c -> shift+enter - when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), +registerSendSequenceKeybinding('\x1b[24~c', { // F12,c -> shift+enter (AddLine) + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), primary: KeyMod.Shift | KeyCode.Enter }); -registerSendSequenceKeybinding('\x1b[24~d', { // F12,d -> shift+end - \x1b[1;2F is supposed to work but it doesn't - when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell)), +registerSendSequenceKeybinding('\x1b[24~d', { // F12,d -> shift+end (SelectLine) - HACK: \x1b[1;2F is supposed to work but it doesn't + when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), mac: { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.RightArrow } }); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index b791e1e8472..0c8ca3f7859 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -46,7 +46,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { TerminalCapabilityStoreMultiplexer } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; -import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, PosixShellType, ProcessPropertyType, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal'; +import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, PosixShellType, ProcessPropertyType, ShellIntegrationStatus, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal'; import { escapeNonWindowsPath, collapseTildePath } from 'vs/platform/terminal/common/terminalEnvironment'; import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -356,6 +356,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private readonly _terminalShellTypeContextKey: IContextKey, private readonly _terminalAltBufferActiveContextKey: IContextKey, private readonly _terminalInRunCommandPicker: IContextKey, + private readonly _terminalShellIntegrationEnabledContextKey: IContextKey, private readonly _configHelper: TerminalConfigHelper, private _shellLaunchConfig: IShellLaunchConfig, resource: URI | undefined, @@ -1083,6 +1084,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const screenElement = xterm.attachToElement(xtermElement); xterm.onDidChangeFindResults((results) => this._onDidChangeFindResults.fire(results)); + xterm.shellIntegration.onDidChangeStatus(() => { + if (this.hasFocus) { + this._setShellIntegrationContextKey(); + } else { + this._terminalShellIntegrationEnabledContextKey.reset(); + } + }); if (!xterm.raw.element || !xterm.raw.textarea) { throw new Error('xterm elements not set after open'); @@ -1216,16 +1224,25 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _setFocus(focused?: boolean): void { if (focused) { this._terminalFocusContextKey.set(true); + this._setShellIntegrationContextKey(); this._onDidFocus.fire(this); } else { - this._terminalFocusContextKey.reset(); + this.resetFocusContextKey(); this._onDidBlur.fire(this); this._refreshSelectionContextKey(); } } + private _setShellIntegrationContextKey(): void { + console.log('set', this.xterm?.shellIntegration.status === ShellIntegrationStatus.VSCode); + if (this.xterm) { + this._terminalShellIntegrationEnabledContextKey.set(this.xterm.shellIntegration.status === ShellIntegrationStatus.VSCode); + } + } + resetFocusContextKey(): void { this._terminalFocusContextKey.reset(); + this._terminalShellIntegrationEnabledContextKey.reset(); } private _initDragAndDrop(container: HTMLElement) { @@ -1301,6 +1318,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } const terminalFocused = !isFocused && (document.activeElement === this.xterm.raw.textarea || document.activeElement === this.xterm.raw.element); this._terminalFocusContextKey.set(terminalFocused); + if (terminalFocused) { + this._setShellIntegrationContextKey(); + } else { + this._terminalShellIntegrationEnabledContextKey.reset(); + } } private _refreshAltBufferContextKey() { @@ -1381,7 +1403,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // as 'blur' event in xterm.raw.textarea is not triggered on xterm.dispose() // See https://github.com/microsoft/vscode/issues/138358 if (isFirefox) { - this._terminalFocusContextKey.reset(); + this.resetFocusContextKey(); this._terminalHasTextContextKey.reset(); this._onDidBlur.fire(this); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index a7732ab7c62..a9feaab7aa1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -24,6 +24,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst private _terminalShellTypeContextKey: IContextKey; private _terminalAltBufferActiveContextKey: IContextKey; private _terminalInRunCommandPicker: IContextKey; + private _terminalShellIntegrationEnabled: IContextKey; private _configHelper: TerminalConfigHelper; private readonly _onDidCreateInstance = new Emitter(); @@ -39,6 +40,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst this._terminalShellTypeContextKey = TerminalContextKeys.shellType.bindTo(this._contextKeyService); this._terminalAltBufferActiveContextKey = TerminalContextKeys.altBufferActive.bindTo(this._contextKeyService); this._terminalInRunCommandPicker = TerminalContextKeys.inTerminalRunCommandPicker.bindTo(this._contextKeyService); + this._terminalShellIntegrationEnabled = TerminalContextKeys.terminalShellIntegrationEnabled.bindTo(this._contextKeyService); this._configHelper = _instantiationService.createInstance(TerminalConfigHelper); } @@ -52,6 +54,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst this._terminalShellTypeContextKey, this._terminalAltBufferActiveContextKey, this._terminalInRunCommandPicker, + this._terminalShellIntegrationEnabled, this._configHelper, shellLaunchConfig, resource diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 1d292d45536..b97b332ea27 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -32,6 +32,7 @@ export const enum TerminalContextKeyStrings { SplitTerminal = 'terminalSplitTerminal', ShellType = 'terminalShellType', InTerminalRunCommandPicker = 'inTerminalRunCommandPicker', + TerminalShellIntegrationEnabled = 'terminalShellIntegrationEnabled' } export namespace TerminalContextKeys { @@ -123,4 +124,7 @@ export namespace TerminalContextKeys { /** Whether the terminal run command picker is currently open. */ export const inTerminalRunCommandPicker = new RawContextKey(TerminalContextKeyStrings.InTerminalRunCommandPicker, false, localize('inTerminalRunCommandPickerContextKey', "Whether the terminal run command picker is currently open.")); + + /** Whether shell integration is enabled in the active terminal. This only considers full VS Code shell integration. */ + export const terminalShellIntegrationEnabled = new RawContextKey(TerminalContextKeyStrings.TerminalShellIntegrationEnabled, false, localize('terminalShellIntegrationEnabled', "Whether shell integration is enabled in the active terminal")); } -- cgit v1.2.3 From e9fc1fb99fa8e8f34cd0ddec9547ded57a685923 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 11 Jul 2022 10:34:38 +0200 Subject: ignore syncing settings profile enablement setting (#154771) --- src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 13f2e154c6d..0c2b7990a49 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -63,7 +63,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements 'type': 'boolean', 'default': false, 'description': localize('workbench.experimental.settingsProfiles.enabled', "Controls whether to enable the Settings Profiles preview feature."), - scope: ConfigurationScope.APPLICATION + scope: ConfigurationScope.APPLICATION, + ignoreSync: true } } }); -- cgit v1.2.3 From f4f0ab3c189798a6a1d400742450571963de1278 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 11 Jul 2022 11:08:14 +0200 Subject: TypeError: Cannot read properties of null (reading 'uri') (#154775) Fixes #154764 --- src/vs/workbench/contrib/comments/browser/commentReply.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index 8fe2a07420e..68eda2e6e39 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -218,8 +218,8 @@ export class CommentReply extends Disposable { this._commentThreadDisposables.push(this._commentThread.onDidChangeInput(input => { const thread = this._commentThread; - - if (thread.input && thread.input.uri !== commentEditor.getModel()!.uri) { + const model = commentEditor.getModel(); + if (thread.input && model && (thread.input.uri !== model.uri)) { return; } if (!input) { -- cgit v1.2.3 From 21147b8c9827809b5dc7db1236b0ff8ccf6c1cd2 Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 11 Jul 2022 14:19:15 +0200 Subject: make `MenuId#id` a string and a strict identifier --- src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index b3c51e8a568..271fe4a1ec5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -446,7 +446,7 @@ async function runAction(action: IAction): Promise { } interface IExtensionActionOptions extends IAction2Options { - menuTitles?: { [id: number]: string }; + menuTitles?: { [id: string]: string }; run(accessor: ServicesAccessor, ...args: any[]): Promise; } -- cgit v1.2.3 From 26852e0bcdec25c2f44fabd1ae9dfbcbc2ef2edb Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 11 Jul 2022 14:44:19 +0200 Subject: support to hide menu item from their context menu, have persisted menu item hide states in menu service, create `MenuItemAction` with an util that can hide itself and its siblings (from the same menu), some adoptions --- .../notebook/browser/controller/editActions.ts | 1 + .../notebook/browser/diff/diffComponents.ts | 2 +- .../notebook/browser/diff/notebookTextDiffList.ts | 2 +- .../browser/view/cellParts/cellActionView.ts | 25 +--------------------- .../browser/view/cellParts/cellToolbars.ts | 2 +- .../browser/viewParts/notebookEditorToolbar.ts | 6 +++--- .../browser/viewParts/notebookTopCellToolbar.ts | 2 +- .../contrib/terminal/browser/terminalMenus.ts | 11 +++++----- .../contrib/terminal/browser/terminalView.ts | 5 +++-- .../contrib/testing/browser/testingExplorerView.ts | 2 +- 10 files changed, 19 insertions(+), 39 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index ebcdf94c782..1f64bddb5e3 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -49,6 +49,7 @@ export class DeleteCellAction extends MenuItemAction { }, undefined, { shouldForwardArgs: true }, + undefined, contextKeyService, commandService); } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index c7d1f0d6594..da1a9c3a04a 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -156,7 +156,7 @@ class PropertyHeader extends Disposable { this._toolbar = new ToolBar(cellToolbarContainer, this.contextMenuService, { actionViewItemProvider: action => { if (action instanceof MenuItemAction) { - const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService); + const item = new CodiconActionViewItem(action, undefined, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService); return item; } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts index 07374920577..244f65f50ca 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts @@ -187,7 +187,7 @@ export class CellDiffSideBySideRenderer implements IListRenderer { if (action instanceof MenuItemAction) { - const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService); + const item = new CodiconActionViewItem(action, undefined, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService); return item; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts index fe682e0e9aa..1808c007e8f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts @@ -6,22 +6,9 @@ import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import * as DOM from 'vs/base/browser/dom'; import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; -import { MenuItemAction } from 'vs/platform/actions/common/actions'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; export class CodiconActionViewItem extends MenuEntryActionViewItem { - constructor( - _action: MenuItemAction, - @IKeybindingService keybindingService: IKeybindingService, - @INotificationService notificationService: INotificationService, - @IContextKeyService contextKeyService: IContextKeyService, - @IThemeService themeService: IThemeService, - ) { - super(_action, undefined, keybindingService, notificationService, contextKeyService, themeService); - } + override updateLabel(): void { if (this.options.label && this.label) { DOM.reset(this.label, ...renderLabelWithIcons(this._commandAction.label ?? '')); @@ -32,16 +19,6 @@ export class CodiconActionViewItem extends MenuEntryActionViewItem { export class ActionViewWithLabel extends MenuEntryActionViewItem { private _actionLabel?: HTMLAnchorElement; - constructor( - _action: MenuItemAction, - @IKeybindingService keybindingService: IKeybindingService, - @INotificationService notificationService: INotificationService, - @IContextKeyService contextKeyService: IContextKeyService, - @IThemeService themeService: IThemeService, - ) { - super(_action, undefined, keybindingService, notificationService, contextKeyService, themeService); - } - override render(container: HTMLElement): void { super.render(container); container.classList.add('notebook-action-view-item'); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts index c84443b70ab..35502e4e1a9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -42,7 +42,7 @@ export class BetweenCellToolbar extends CellPart { actionViewItemProvider: action => { if (action instanceof MenuItemAction) { if (this._notebookEditor.notebookOptions.getLayoutConfiguration().insertToolbarAlignment === 'center') { - return instantiationService.createInstance(CodiconActionViewItem, action); + return instantiationService.createInstance(CodiconActionViewItem, action, undefined); } else { return instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts index 1d1d38c2689..c80acf0d2ec 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts @@ -69,7 +69,7 @@ class FixedLabelStrategy implements IActionLayoutStrategy { const a = this.editorToolbar.primaryActions.find(a => a.action.id === action.id); if (a && a.renderLabel) { - return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action) : undefined; + return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined; } else { return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined; } @@ -144,7 +144,7 @@ class DynamicLabelStrategy implements IActionLayoutStrategy { const a = this.editorToolbar.primaryActions.find(a => a.action.id === action.id); if (a && a.renderLabel) { - return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action) : undefined; + return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined; } else { return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined; } @@ -360,7 +360,7 @@ export class NotebookEditorToolbar extends Disposable { if (this._renderLabel !== RenderLabel.Never) { const a = this._primaryActions.find(a => a.action.id === action.id); if (a && a.renderLabel) { - return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action) : undefined; + return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined; } else { return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined; } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts index 92d6c18e45f..ab97127c986 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts @@ -37,7 +37,7 @@ export class ListTopCellToolbar extends Disposable { this.toolbar = this._register(new ToolBar(this.topCellToolbar, this.contextMenuService, { actionViewItemProvider: action => { if (action instanceof MenuItemAction) { - const item = this.instantiationService.createInstance(CodiconActionViewItem, action); + const item = this.instantiationService.createInstance(CodiconActionViewItem, action, undefined); return item; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts index c6e6700e280..af2d55a2242 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts @@ -752,11 +752,11 @@ export function getTerminalActionBarArgs(location: ITerminalLocationOptions, pro shouldForwardArgs: true }; if (isDefault) { - dropdownActions.unshift(new MenuItemAction({ id: TerminalCommandId.NewWithProfile, title: localize('defaultTerminalProfile', "{0} (Default)", p.profileName), category: TerminalTabContextMenuGroup.Profile }, undefined, options, contextKeyService, commandService)); - submenuActions.unshift(new MenuItemAction({ id: TerminalCommandId.Split, title: localize('defaultTerminalProfile', "{0} (Default)", p.profileName), category: TerminalTabContextMenuGroup.Profile }, undefined, splitOptions, contextKeyService, commandService)); + dropdownActions.unshift(new MenuItemAction({ id: TerminalCommandId.NewWithProfile, title: localize('defaultTerminalProfile', "{0} (Default)", p.profileName), category: TerminalTabContextMenuGroup.Profile }, undefined, options, undefined, contextKeyService, commandService)); + submenuActions.unshift(new MenuItemAction({ id: TerminalCommandId.Split, title: localize('defaultTerminalProfile', "{0} (Default)", p.profileName), category: TerminalTabContextMenuGroup.Profile }, undefined, splitOptions, undefined, contextKeyService, commandService)); } else { - dropdownActions.push(new MenuItemAction({ id: TerminalCommandId.NewWithProfile, title: p.profileName.replace(/[\n\r\t]/g, ''), category: TerminalTabContextMenuGroup.Profile }, undefined, options, contextKeyService, commandService)); - submenuActions.push(new MenuItemAction({ id: TerminalCommandId.Split, title: p.profileName.replace(/[\n\r\t]/g, ''), category: TerminalTabContextMenuGroup.Profile }, undefined, splitOptions, contextKeyService, commandService)); + dropdownActions.push(new MenuItemAction({ id: TerminalCommandId.NewWithProfile, title: p.profileName.replace(/[\n\r\t]/g, ''), category: TerminalTabContextMenuGroup.Profile }, undefined, options, undefined, contextKeyService, commandService)); + submenuActions.push(new MenuItemAction({ id: TerminalCommandId.Split, title: p.profileName.replace(/[\n\r\t]/g, ''), category: TerminalTabContextMenuGroup.Profile }, undefined, splitOptions, undefined, contextKeyService, commandService)); } } @@ -823,7 +823,8 @@ export function getTerminalActionBarArgs(location: ITerminalLocationOptions, pro { shouldForwardArgs: true, arg: { location } as ICreateTerminalOptions, - }); + }, + undefined); const dropdownAction = new Action('refresh profiles', 'Launch Profile...', 'codicon-chevron-down', true); return { primaryAction, dropdownAction, dropdownMenuActions: dropdownActions, className: `terminal-tab-actions-${terminalService.resolveLocation(location)}` }; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index f3f77734984..9dc9017d20f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -373,7 +373,7 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { @IThemeService themeService: IThemeService, @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService, - @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IContextMenuService contextMenuService: IContextMenuService, @ICommandService private readonly _commandService: ICommandService, @IConfigurationService configurationService: IConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -390,11 +390,12 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { icon: Codicon.splitHorizontal }, undefined, + undefined, contextKeyService, _commandService ), { draggable: true - }, keybindingService, notificationService, contextKeyService, themeService); + }, keybindingService, notificationService, contextKeyService, themeService, contextMenuService); // Register listeners to update the tab this._register(this._terminalService.onDidChangeInstancePrimaryStatus(e => this.updateLabel(e))); diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 0c639a29c53..66ffbef2595 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -337,7 +337,7 @@ export class TestingExplorerView extends ViewPane { icon: group === TestRunProfileBitset.Run ? icons.testingRunAllIcon : icons.testingDebugAllIcon, - }, undefined, undefined); + }, undefined, undefined, undefined); const dropdownAction = new Action('selectRunConfig', 'Select Configuration...', 'codicon-chevron-down', true); -- cgit v1.2.3 From 5b288e119180046a1e00003357293c8ee04a4782 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 11 Jul 2022 11:10:01 -0700 Subject: Improve tasks localized strings Part of #153743 --- src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 1a3261a3335..93cbe879116 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -2740,7 +2740,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } this._showQuickPick(tasks ? tasks : taskResult!.tasks, placeholder, { - label: nls.localize('TaskService.noEntryToRunSlow', '$(plus) Configure a Task'), + label: '$(plus) ' + nls.localize('TaskService.noEntryToRun', 'Configure a Task'), task: null }, true). @@ -2750,7 +2750,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } else { this._showTwoLevelQuickPick(placeholder, { - label: nls.localize('TaskService.noEntryToRun', '$(plus) Configure a Task'), + label: '$(plus) ' + nls.localize('TaskService.noEntryToRun', 'Configure a Task'), task: null }). then(pickThen); -- cgit v1.2.3 From 75bc124fe92fe08a31afb41cac0eed9135abbfde Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 11 Jul 2022 11:35:10 -0700 Subject: Remove todo --- src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index 60277418746..a62e8b54d38 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -177,7 +177,6 @@ if (isWindows) { }); } -// TODO: This only works when shell integration is enabled - create shell integration enabled for active terminal context key // Map certain keybindings in pwsh to unused keys which get handled by PSReadLine handlers in the // shell integration script. This allows keystrokes that cannot be sent via VT sequences to work. // See https://github.com/microsoft/terminal/issues/879#issuecomment-497775007 -- cgit v1.2.3 From 88f9806a976e9e77efa4e9d711cc0ecc07671ec9 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Mon, 11 Jul 2022 16:04:45 -0400 Subject: Add installation context to install extension command (#154846) * Add installation context to install extension command * Update src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts Co-authored-by: Joyce Er Co-authored-by: Joyce Er --- .../contrib/extensions/browser/extensions.contribution.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 271fe4a1ec5..f53f6a7de8a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -78,6 +78,7 @@ import { isWeb } from 'vs/base/common/platform'; import { ExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { IStorageService } from 'vs/platform/storage/common/storage'; import product from 'vs/platform/product/common/product'; +import { IStringDictionary } from 'vs/base/common/collections'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -316,13 +317,17 @@ CommandsRegistry.registerCommand({ 'type': 'boolean', 'description': localize('workbench.extensions.installExtension.option.donotSync', "When enabled, VS Code do not sync this extension when Settings Sync is on."), default: false + }, + 'context': { + 'type': 'object', + 'description': localize('workbench.extensions.installExtension.option.context', "Context for the installation. This is a JSON object that can be used to pass any information to the installation handlers. i.e. `{skipWalkthrough: true}` will skip opening the walkthrough upon install."), } } } } ] }, - handler: async (accessor, arg: string | UriComponents, options?: { installOnlyNewlyAddedFromExtensionPackVSIX?: boolean; installPreReleaseVersion?: boolean; donotSync?: boolean }) => { + handler: async (accessor, arg: string | UriComponents, options?: { installOnlyNewlyAddedFromExtensionPackVSIX?: boolean; installPreReleaseVersion?: boolean; donotSync?: boolean; context?: IStringDictionary }) => { const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); try { if (typeof arg === 'string') { @@ -332,7 +337,8 @@ CommandsRegistry.registerCommand({ const installOptions: InstallOptions = { isMachineScoped: options?.donotSync ? true : undefined, /* do not allow syncing extensions automatically while installing through the command */ installPreReleaseVersion: options?.installPreReleaseVersion, - installGivenVersion: !!version + installGivenVersion: !!version, + context: options?.context }; if (version) { await extensionsWorkbenchService.installVersion(extension, version, installOptions); -- cgit v1.2.3 From e7e987e3b3444d56fa19695da4dd81f7c1dbfb28 Mon Sep 17 00:00:00 2001 From: Robert Jin <20613660+jzyrobert@users.noreply.github.com> Date: Mon, 11 Jul 2022 21:30:13 +0100 Subject: Add Expand all button in explorer view (#153614) Add expand all button in explorer view to replace collapse all if all workspace root folders are collapsed --- .../contrib/files/browser/views/explorerView.ts | 78 +++++++++++++++++++--- src/vs/workbench/contrib/files/common/files.ts | 2 + 2 files changed, 71 insertions(+), 9 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 1e2e605ec5e..6185a906efa 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import * as perf from 'vs/base/common/performance'; import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { memoize } from 'vs/base/common/decorators'; -import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext, VIEW_ID, VIEWLET_ID, ExplorerResourceNotReadonlyContext } from 'vs/workbench/contrib/files/common/files'; +import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext, VIEW_ID, VIEWLET_ID, ExplorerResourceNotReadonlyContext, ViewHasSomeCollapsibleRootItemContext } from 'vs/workbench/contrib/files/common/files'; import { FileCopiedContext, NEW_FILE_COMMAND_ID, NEW_FOLDER_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileActions'; import * as DOM from 'vs/base/browser/dom'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -67,13 +67,19 @@ interface IExplorerViewStyles { listDropBackground?: Color; } -function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree, treeInput: ExplorerItem[]): boolean { - for (const folder of treeInput) { - if (tree.hasNode(folder) && !tree.isCollapsed(folder)) { - for (const [, child] of folder.children.entries()) { - if (tree.hasNode(child) && tree.isCollapsible(child) && !tree.isCollapsed(child)) { - return true; - } +// Accepts a single or multiple workspace folders +function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree, treeInput: ExplorerItem | ExplorerItem[]): boolean { + const inputsToCheck = []; + if (Array.isArray(treeInput)) { + inputsToCheck.push(...treeInput.filter(folder => tree.hasNode(folder) && !tree.isCollapsed(folder))); + } else { + inputsToCheck.push(treeInput); + } + + for (const folder of inputsToCheck) { + for (const [, child] of folder.children.entries()) { + if (tree.hasNode(child) && tree.isCollapsible(child) && !tree.isCollapsed(child)) { + return true; } } } @@ -161,6 +167,8 @@ export class ExplorerView extends ViewPane implements IExplorerView { private compressedFocusFirstContext: IContextKey; private compressedFocusLastContext: IContextKey; + private viewHasSomeCollapsibleRootItem: IContextKey; + private horizontalScrolling: boolean | undefined; private dragHandler!: DelayedDragHandler; @@ -207,6 +215,7 @@ export class ExplorerView extends ViewPane implements IExplorerView { this.compressedFocusContext = ExplorerCompressedFocusContext.bindTo(contextKeyService); this.compressedFocusFirstContext = ExplorerCompressedFirstFocusContext.bindTo(contextKeyService); this.compressedFocusLastContext = ExplorerCompressedLastFocusContext.bindTo(contextKeyService); + this.viewHasSomeCollapsibleRootItem = ViewHasSomeCollapsibleRootItemContext.bindTo(contextKeyService); this.explorerService.registerView(this); } @@ -493,6 +502,9 @@ export class ExplorerView extends ViewPane implements IExplorerView { } })); + this._register(this.tree.onDidChangeCollapseState(() => this.updateAnyCollapsedContext())); + this.updateAnyCollapsedContext(); + this._register(this.tree.onMouseDblClick(e => { if (e.element === null) { // click in empty area -> create a new file #116676 @@ -769,6 +781,23 @@ export class ExplorerView extends ViewPane implements IExplorerView { } } + expandAll(): void { + if (this.explorerService.isEditable(undefined)) { + this.tree.domFocus(); + } + + const treeInput = this.tree.getInput(); + if (Array.isArray(treeInput)) { + treeInput.forEach(folder => { + folder.children.forEach(child => this.tree.hasNode(child) && this.tree.expand(child, true)); + }); + + return; + } + + this.tree.expandAll(); + } + collapseAll(): void { if (this.explorerService.isEditable(undefined)) { this.tree.domFocus(); @@ -837,6 +866,14 @@ export class ExplorerView extends ViewPane implements IExplorerView { this.compressedFocusLastContext.set(controller.index === controller.count - 1); } + private updateAnyCollapsedContext(): void { + const treeInput = this.tree.getInput(); + if (treeInput === undefined) { + return; + } + this.viewHasSomeCollapsibleRootItem.set(hasExpandedRootChild(this.tree, treeInput)); + } + styleListDropBackground(styles: IExplorerViewStyles): void { const content: string[] = []; @@ -951,7 +988,7 @@ registerAction2(class extends Action2 { menu: { id: MenuId.ViewTitle, group: 'navigation', - when: ContextKeyExpr.equals('view', VIEW_ID), + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), ViewHasSomeCollapsibleRootItemContext), order: 40 } }); @@ -963,3 +1000,26 @@ registerAction2(class extends Action2 { explorerView.collapseAll(); } }); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'workbench.files.action.expandExplorerFolders', + title: { value: nls.localize('expandExplorerFolders', "Expand Folders in Explorer"), original: 'Expand Folders in Explorer' }, + f1: true, + icon: Codicon.expandAll, + menu: { + id: MenuId.ViewTitle, + group: 'navigation', + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), ViewHasSomeCollapsibleRootItemContext.toNegated()), + order: 40 + } + }); + } + + run(accessor: ServicesAccessor) { + const viewsService = accessor.get(IViewsService); + const explorerView = viewsService.getViewWithId(VIEW_ID) as ExplorerView; + explorerView.expandAll(); + } +}); diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index d903a67b219..b9500df93d9 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -56,6 +56,8 @@ export const ExplorerCompressedFocusContext = new RawContextKey('explor export const ExplorerCompressedFirstFocusContext = new RawContextKey('explorerViewletCompressedFirstFocus', true, { type: 'boolean', description: localize('explorerViewletCompressedFirstFocus', "True when the focus is inside a compact item's first part in the EXPLORER view.") }); export const ExplorerCompressedLastFocusContext = new RawContextKey('explorerViewletCompressedLastFocus', true, { type: 'boolean', description: localize('explorerViewletCompressedLastFocus', "True when the focus is inside a compact item's last part in the EXPLORER view.") }); +export const ViewHasSomeCollapsibleRootItemContext = new RawContextKey('viewHasSomeCollapsibleItem', false, { type: 'boolean', description: localize('viewHasSomeCollapsibleItem', "True when a workspace in the EXPLORER view has some collapsible root child.") }); + export const FilesExplorerFocusCondition = ContextKeyExpr.and(ExplorerViewletVisibleContext, FilesExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey)); export const ExplorerFocusCondition = ContextKeyExpr.and(ExplorerViewletVisibleContext, ExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey)); -- cgit v1.2.3 From 112b2d050c8398e74360fcb2219af76ad51e8aa5 Mon Sep 17 00:00:00 2001 From: Stephen Sigwart Date: Mon, 11 Jul 2022 20:55:20 -0400 Subject: Fix menu shortcuts not working after a webview is shown (#154648) Release notes, markdown preview, etc. use a web view. The web view uses `WindowIgnoreMenuShortcutsManager`/`allowMenuShortcuts` to disable menu shortcuts. However, `WindowIgnoreMenuShortcutsManager.didBlur` isn't being called when the editor is closed, so the menu shortcuts stay disabled. This seems to be a side effect of the fix for #82670. This does not solve using Cmd+M or Cmd+H while focused on a web view. I looked into detecting those keys and enabling menu shortcuts, but it seems like MacOS still won't detect them if you enable menu shortcuts after the key stroke is pressed but before the event is finished processing. --- .../workbench/contrib/webview/electron-sandbox/webviewElement.ts | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts index 692eeb591ba..c119358978c 100644 --- a/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts @@ -90,6 +90,13 @@ export class ElectronWebviewElement extends WebviewElement { } } + override dispose(): void { + // Make sure keyboard handler knows it closed (#71800) + this._webviewKeyboardHandler.didBlur(); + + super.dispose(); + } + protected override webviewContentEndpoint(iframeId: string): string { return `${Schemas.vscodeWebview}://${iframeId}`; } -- cgit v1.2.3 From 5a2c591b822fa4e5d4a15bb73786b4564e9825ec Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 12 Jul 2022 07:12:25 +0200 Subject: update nls comment (#153743) (#154890) --- src/vs/workbench/contrib/files/browser/files.contribution.ts | 2 +- src/vs/workbench/contrib/search/browser/search.contribution.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 0db2db173e1..1ebf70ce257 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -159,7 +159,7 @@ configurationRegistry.registerConfiguration({ 'type': 'string', // expression ({ "**/*.js": { "when": "$(basename).js" } }) 'pattern': '\\w*\\$\\(basename\\)\\w*', 'default': '$(basename).ext', - 'markdownDescription': nls.localize('files.exclude.when', "Additional check on the siblings of a matching file. Use \\$(basename) as variable for the matching file name.") + 'markdownDescription': nls.localize({ key: 'files.exclude.when', comment: ['\\$(basename) should not be translated'] }, "Additional check on the siblings of a matching file. Use \\$(basename) as variable for the matching file name.") } } } diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 54450eb71c4..099169c6ab3 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -844,7 +844,7 @@ configurationRegistry.registerConfiguration({ type: 'string', // expression ({ "**/*.js": { "when": "$(basename).js" } }) pattern: '\\w*\\$\\(basename\\)\\w*', default: '$(basename).ext', - markdownDescription: nls.localize('exclude.when', 'Additional check on the siblings of a matching file. Use \\$(basename) as variable for the matching file name.') + markdownDescription: nls.localize({ key: 'exclude.when', comment: ['\\$(basename) should not be translated'] }, 'Additional check on the siblings of a matching file. Use \\$(basename) as variable for the matching file name.') } } } -- cgit v1.2.3 From 51507ba87a14205209cfd202bfcb2abe2d249fa6 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 12 Jul 2022 11:35:25 +0200 Subject: make sure to pass the right height to editors, (#154900) fixes https://github.com/microsoft/vscode/issues/154765 --- .../contrib/mergeEditor/browser/view/editors/codeEditorView.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index f6a409656f8..321d1594dd5 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -24,7 +24,7 @@ export abstract class CodeEditorView extends Disposable { readonly model = this._viewModel.map(m => /** @description model */ m?.model); protected readonly htmlElements = h('div.code-view', [ - h('div.title', [ + h('div.title', { $: 'header' }, [ h('span.title', { $: 'title' }), h('span.description', { $: 'description' }), h('span.detail', { $: 'detail' }), @@ -48,7 +48,7 @@ export abstract class CodeEditorView extends Disposable { setStyle(this.htmlElements.root, { width, height, top, left }); this.editor.layout({ width: width - this.htmlElements.gutterDiv.clientWidth, - height: height - this.htmlElements.title.clientHeight, + height: height - this.htmlElements.header.clientHeight, }); } // preferredWidth?: number | undefined; -- cgit v1.2.3 From 04b95fcebbda869fc2097fb967e647b0138a10d9 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 12 Jul 2022 11:36:41 +0200 Subject: Commit Action button polish (#154908) More Commit Action button tweaks --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 41cce292993..9af7e3e8a77 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2668,6 +2668,7 @@ export class SCMActionButton implements IDisposable { actions: actions, addPrimaryActionToDropdown: false, contextMenuProvider: this.contextMenuService, + title: button.command.tooltip, supportIcons: true }); } else if (button.description) { @@ -2681,7 +2682,6 @@ export class SCMActionButton implements IDisposable { this.button.enabled = button.enabled; this.button.label = button.command.title; - this.button.element.title = button.command.tooltip ?? ''; this.button.onDidClick(async () => await executeButtonAction(button.command.id, ...(button.command.arguments || [])), null, this.disposables.value); this.disposables.value!.add(this.button); -- cgit v1.2.3 From 725f83e64094c6952a1109479d5bef533bf50f50 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 12 Jul 2022 14:38:42 +0200 Subject: show set display language action for lang pack extensions --- .../contrib/extensions/browser/extensionEditor.ts | 3 +- .../extensions/browser/extensionsActions.ts | 61 +++++++++++++++++++--- .../contrib/extensions/browser/extensionsList.ts | 22 +++++--- .../browser/extensionsWorkbenchService.ts | 34 +++++++++++- .../contrib/extensions/common/extensions.ts | 2 + 5 files changed, 108 insertions(+), 14 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 381096c914f..c8632a19ecb 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -29,7 +29,7 @@ import { UpdateAction, ReloadAction, EnableDropDownAction, DisableDropDownAction, ExtensionStatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ToggleSyncExtensionAction, SetProductIconThemeAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, UninstallAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, - InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction + InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction, SetLanguageAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -329,6 +329,7 @@ export class ExtensionEditor extends EditorPane { this.instantiationService.createInstance(EnableDropDownAction), this.instantiationService.createInstance(DisableDropDownAction), + this.instantiationService.createInstance(SetLanguageAction), this.instantiationService.createInstance(RemoteInstallAction, false), this.instantiationService.createInstance(LocalInstallAction), this.instantiationService.createInstance(WebInstallAction), diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index c6d34dc012f..ff05091ffb0 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -56,7 +56,7 @@ import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; import { ILogService } from 'vs/platform/log/common/log'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { errorIcon, infoIcon, manageExtensionIcon, preReleaseIcon, syncEnabledIcon, syncIgnoredIcon, trustIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; -import { isIOS, isWeb } from 'vs/base/common/platform'; +import { isIOS, isWeb, language } from 'vs/base/common/platform'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { isVirtualWorkspace } from 'vs/platform/workspace/common/virtualWorkspace'; @@ -66,6 +66,7 @@ import { ViewContainerLocation } from 'vs/workbench/common/views'; import { flatten } from 'vs/base/common/arrays'; import { fromNow } from 'vs/base/common/date'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; export class PromptExtensionInstallFailureAction extends Action { @@ -264,11 +265,18 @@ export abstract class AbstractInstallAction extends ExtensionAction { protected async computeAndUpdateEnablement(): Promise { this.enabled = false; - if (this.extension && !this.extension.isBuiltin) { - if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) { - this.enabled = this.installPreReleaseVersion ? this.extension.hasPreReleaseVersion : this.extension.hasReleaseVersion; - this.updateLabel(); - } + if (!this.extension) { + return; + } + if (this.extension.isBuiltin) { + return; + } + if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) { + return; + } + if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) { + this.enabled = this.installPreReleaseVersion ? this.extension.hasPreReleaseVersion : this.extension.hasReleaseVersion; + this.updateLabel(); } } @@ -1767,6 +1775,43 @@ export class SetProductIconThemeAction extends ExtensionAction { } } +export class SetLanguageAction extends ExtensionAction { + + static readonly ID = 'workbench.extensions.action.setLanguageTheme'; + static readonly TITLE = { value: localize('workbench.extensions.action.setLanguageTheme', "Set Display Language"), original: 'Set Display Language' }; + + private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; + private static readonly DisabledClass = `${SetLanguageAction.EnabledClass} disabled`; + + constructor( + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @ILanguagePackService private readonly languagePackService: ILanguagePackService, + ) { + super(SetLanguageAction.ID, SetLanguageAction.TITLE.value, SetLanguageAction.DisabledClass, false); + this.update(); + } + + update(): void { + this.enabled = false; + this.class = SetLanguageAction.DisabledClass; + if (!this.extension) { + return; + } + if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) { + return; + } + if (this.extension.gallery && language === this.languagePackService.getLocale(this.extension.gallery)) { + return; + } + this.enabled = true; + this.class = SetLanguageAction.EnabledClass; + } + + override async run(): Promise { + return this.extension && this.extensionsWorkbenchService.setLanguage(this.extension); + } +} + export class ShowRecommendedExtensionAction extends Action { static readonly ID = 'workbench.extensions.action.showRecommendedExtension'; @@ -2259,6 +2304,10 @@ export class ExtensionStatusAction extends ExtensionAction { return; } + if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) { + return; + } + if (this.extension.gallery && this.extension.state === ExtensionState.Uninstalled && !await this.extensionsWorkbenchService.canInstall(this.extension)) { if (this.extensionManagementServerService.localExtensionManagementServer || this.extensionManagementServerService.remoteExtensionManagementServer) { const targetPlatform = await (this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.getTargetPlatform() : this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getTargetPlatform()); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 1754e2233ab..84eaa15be18 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -13,7 +13,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { Event } from 'vs/base/common/event'; import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; -import { UpdateAction, ManageExtensionAction, ReloadAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { UpdateAction, ManageExtensionAction, ReloadAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction, SetLanguageAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget, ExtensionHoverWidget, ExtensionActivationStatusWidget, PreReleaseBookmarkWidget, extensionVerifiedPublisherIconColor } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions'; @@ -123,6 +123,7 @@ export class Renderer implements IPagedRenderer { reloadAction, this.instantiationService.createInstance(InstallDropdownAction), this.instantiationService.createInstance(InstallingLabelAction), + this.instantiationService.createInstance(SetLanguageAction), this.instantiationService.createInstance(RemoteInstallAction, false), this.instantiationService.createInstance(LocalInstallAction), this.instantiationService.createInstance(WebInstallAction), @@ -186,16 +187,25 @@ export class Renderer implements IPagedRenderer { data.extensionDisposables = dispose(data.extensionDisposables); - const updateEnablement = async () => { - let disabled = false; - const deprecated = !!extension.deprecationInfo; + const computeEnablement = async () => { if (extension.state === ExtensionState.Uninstalled) { - disabled = deprecated || !(await this.extensionsWorkbenchService.canInstall(extension)); + if (!!extension.deprecationInfo) { + return true; + } + if (this.extensionsWorkbenchService.canSetLanguage(extension)) { + return false; + } + return !(await this.extensionsWorkbenchService.canInstall(extension)); } else if (extension.local && !isLanguagePackExtension(extension.local.manifest)) { const runningExtensions = await this.extensionService.getExtensions(); const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier))[0]; - disabled = !(runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension))); + return !(runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension))); } + return false; + }; + const updateEnablement = async () => { + const disabled = await computeEnablement(); + const deprecated = !!extension.deprecationInfo; data.element.classList.toggle('deprecated', deprecated); data.root.classList.toggle('disabled', disabled); }; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index a533f0ad244..6cf90434c7b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -45,8 +45,10 @@ import { isBoolean, isUndefined } from 'vs/base/common/types'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { IExtensionService, IExtensionsStatus } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; -import { isWeb } from 'vs/base/common/platform'; +import { isWeb, language } from 'vs/base/common/platform'; import { GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings'; +import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; +import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; interface IExtensionStateProvider { (extension: Extension): T; @@ -710,6 +712,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, @ILogService private readonly logService: ILogService, @IExtensionService private readonly extensionService: IExtensionService, + @ILanguagePackService private readonly languagePackService: ILanguagePackService, + @ILocaleService private readonly localeService: ILocaleService, ) { super(); const preferPreReleasesValue = configurationService.getValue('_extensions.preferPreReleases'); @@ -1248,6 +1252,34 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return this.installWithProgress(() => this.installFromGallery(extension, gallery, installOptions), gallery.displayName, progressLocation); } + canSetLanguage(extension: IExtension): boolean { + if (!isWeb) { + return false; + } + + if (!extension.gallery) { + return false; + } + + const locale = this.languagePackService.getLocale(extension.gallery); + if (!locale) { + return false; + } + + return true; + } + + async setLanguage(extension: IExtension): Promise { + if (!this.canSetLanguage(extension)) { + throw new Error('Can not set language'); + } + const locale = this.languagePackService.getLocale(extension.gallery!); + if (locale === language) { + return; + } + return this.localeService.setLocale({ id: locale, galleryExtension: extension.gallery, extensionId: extension.identifier.id, label: extension.displayName }); + } + setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise { extensions = Array.isArray(extensions) ? extensions : [extensions]; return this.promptAndSetEnablement(extensions, enablementState); diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 60c5b415c54..ca1a3a5ff3f 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -107,6 +107,8 @@ export interface IExtensionsWorkbenchService { uninstall(extension: IExtension): Promise; installVersion(extension: IExtension, version: string, installOptions?: InstallOptions): Promise; reinstall(extension: IExtension): Promise; + canSetLanguage(extension: IExtension): boolean; + setLanguage(extension: IExtension): Promise; setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise; open(extension: IExtension, options?: IExtensionEditorOptions): Promise; checkForUpdates(): Promise; -- cgit v1.2.3 From 405b8fdbc3f376ef6603723feacfc3faeb544da1 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 12 Jul 2022 15:18:19 +0200 Subject: add `vscode-coi` query when loading webview contents related to https://github.com/microsoft/vscode/issues/137884 --- src/vs/workbench/contrib/webview/browser/webviewElement.ts | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 77aaf6683f9..092d2f13707 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -508,6 +508,10 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD params.purpose = options.purpose; } + if (globalThis.crossOriginIsolated) { + params['vscode-coi'] = '3'; /*COOP+COEP*/ + } + const queryString = new URLSearchParams(params).toString(); // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1754872 -- cgit v1.2.3 From 9ccb9add3f20a4111971483d9e49e8ec244986b6 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 12 Jul 2022 15:28:00 +0200 Subject: make sure to allow COI on webview iframe --- src/vs/workbench/contrib/webview/browser/webviewElement.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index 092d2f13707..a6386ed1d6e 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -475,7 +475,9 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD element.className = `webview ${options.customClasses || ''}`; element.sandbox.add('allow-scripts', 'allow-same-origin', 'allow-forms', 'allow-pointer-lock', 'allow-downloads'); if (!isFirefox) { - element.setAttribute('allow', 'clipboard-read; clipboard-write;'); + element.setAttribute('allow', 'clipboard-read; clipboard-write; cross-origin-isolated;'); + } else { + element.setAttribute('allow', 'cross-origin-isolated;'); } element.style.border = 'none'; element.style.width = '100%'; -- cgit v1.2.3 From cb67591f254d0700991a49d4fb13aa4edea6e640 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 12 Jul 2022 15:51:31 +0200 Subject: `Marked as resolved` marking has poor visibility with high contrast color themes (#154921) Fixes #149464 --- .../contrib/comments/browser/commentsTreeViewer.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 3166fd47195..020ef7eb4d1 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -188,9 +188,21 @@ export class CommentNodeRenderer implements IListRenderer return renderedComment; } + private getIcon(commentCount: number, threadState?: CommentThreadState): Codicon { + if (threadState === CommentThreadState.Unresolved) { + return Codicon.commentUnresolved; + } else if (commentCount === 1) { + return Codicon.comment; + } else { + return Codicon.commentDiscussion; + } + } + renderElement(node: ITreeNode, index: number, templateData: ICommentThreadTemplateData, height: number | undefined): void { const commentCount = node.element.replies.length + 1; - templateData.threadMetadata.icon?.classList.add(...ThemeIcon.asClassNameArray((commentCount === 1) ? Codicon.comment : Codicon.commentDiscussion)); + templateData.threadMetadata.icon.classList.remove(...Array.from(templateData.threadMetadata.icon.classList.values()) + .filter(value => value.startsWith('codicon'))); + templateData.threadMetadata.icon.classList.add(...ThemeIcon.asClassNameArray(this.getIcon(commentCount, node.element.threadState))); if (node.element.threadState !== undefined) { const color = this.getCommentThreadWidgetStateColor(node.element.threadState, this.themeService.getColorTheme()); templateData.threadMetadata.icon.style.setProperty(commentViewThreadStateColorVar, `${color}`); -- cgit v1.2.3 From 6e174529bb92ed514822491559951b34e7d87c5a Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 12 Jul 2022 15:52:08 +0200 Subject: tweak allow rules --- src/vs/workbench/contrib/webview/browser/webviewElement.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index a6386ed1d6e..fcc0777e76a 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -474,11 +474,14 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD element.name = this.id; element.className = `webview ${options.customClasses || ''}`; element.sandbox.add('allow-scripts', 'allow-same-origin', 'allow-forms', 'allow-pointer-lock', 'allow-downloads'); + + const allowRules = ['cross-origin-isolated;']; if (!isFirefox) { + allowRules.push('clipboard-read;', 'clipboard-write;'); element.setAttribute('allow', 'clipboard-read; clipboard-write; cross-origin-isolated;'); - } else { - element.setAttribute('allow', 'cross-origin-isolated;'); } + element.setAttribute('allow', allowRules.join(' ')); + element.style.border = 'none'; element.style.width = '100%'; element.style.height = '100%'; -- cgit v1.2.3 From 28e5d3a4b675077904a4c5679d106f978a6788c0 Mon Sep 17 00:00:00 2001 From: Johannes Date: Tue, 12 Jul 2022 15:53:27 +0200 Subject: add `vscode-coi` argument for nested iframe, add COI allow attribute for nested iframe --- .../contrib/webview/browser/pre/index-no-csp.html | 13 ++++++++++--- src/vs/workbench/contrib/webview/browser/pre/index.html | 15 +++++++++++---- 2 files changed, 21 insertions(+), 7 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html index ec2b7281a65..6469cf31e74 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html @@ -919,14 +919,21 @@ sandboxRules.add('allow-forms'); } newFrame.setAttribute('sandbox', Array.from(sandboxRules).join(' ')); - if (!isFirefox) { - newFrame.setAttribute('allow', options.allowScripts ? 'clipboard-read; clipboard-write;' : ''); + + const allowRules = ['cross-origin-isolated;'] + if(!isFirefox && options.allowScripts) { + allowRules.push('clipboard-read;','clipboard-write;') } + newFrame.setAttribute('allow', allowRules.join(' ')); // We should just be able to use srcdoc, but I wasn't // seeing the service worker applying properly. // Fake load an empty on the correct origin and then write real html // into it to get around this. - newFrame.src = `./fake.html?id=${ID}`; + const fakeUrlParams = new URLSearchParams({id: ID}); + if(globalThis.crossOriginIsolated) { + fakeUrlParams.set('vscode-coi', '3') /*COOP+COEP*/ + } + newFrame.src = `./fake.html?${fakeUrlParams.toString()}`; newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden'; document.body.appendChild(newFrame); diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index 326a076c677..965b90ace22 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -5,7 +5,7 @@ + content="default-src 'none'; script-src 'sha256-vGloSX/Mg/JYMjFOA5bYxbKTao1iYLW/tlq9ME/cEOo=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> Date: Tue, 12 Jul 2022 16:30:19 +0200 Subject: Fix #151921 (#154936) --- .../extensions/browser/extensionsActions.ts | 42 ++++++++++++++-------- 1 file changed, 28 insertions(+), 14 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index c6d34dc012f..2ec6eb12140 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -14,7 +14,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { dispose } from 'vs/base/common/lifecycle'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; -import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode, isTargetPlatformCompatible } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { areSameExtensions, getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -561,6 +561,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, ) { super(id, InstallInOtherServerAction.INSTALL_LABEL, InstallInOtherServerAction.Class, false); this.update(); @@ -635,19 +636,29 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { } override async run(): Promise { - if (!this.extension) { + if (!this.extension?.local) { return; } - if (this.server) { - this.extensionsWorkbenchService.open(this.extension); - alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); - if (this.extension.gallery) { - await this.server.extensionManagementService.installFromGallery(this.extension.gallery, { installPreReleaseVersion: this.extension.local?.preRelease }); - } else { - const vsix = await this.extension.server!.extensionManagementService.zip(this.extension.local!); - await this.server.extensionManagementService.install(vsix); - } + if (!this.extension?.server) { + return; + } + if (!this.server) { + return; + } + this.extensionsWorkbenchService.open(this.extension); + alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); + + const gallery = this.extension.gallery ?? (this.extensionGalleryService.isEnabled() && (await this.extensionGalleryService.getExtensions([this.extension.identifier], CancellationToken.None))[0]); + if (gallery) { + await this.server.extensionManagementService.installFromGallery(gallery, { installPreReleaseVersion: this.extension.local.preRelease }); + return; + } + const targetPlatform = await this.server.extensionManagementService.getTargetPlatform(); + if (!isTargetPlatformCompatible(this.extension.local.targetPlatform, [this.extension.local.targetPlatform], targetPlatform)) { + throw new Error(localize('incompatible', "Can't install '{0}' extension because it is not compatible.", this.extension.identifier.id)); } + const vsix = await this.extension.server.extensionManagementService.zip(this.extension.local); + await this.server.extensionManagementService.install(vsix); } protected abstract getInstallLabel(): string; @@ -660,8 +671,9 @@ export class RemoteInstallAction extends InstallInOtherServerAction { @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, ) { - super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, canInstallAnyWhere, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService); + super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, canInstallAnyWhere, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService, extensionGalleryService); } protected getInstallLabel(): string { @@ -678,8 +690,9 @@ export class LocalInstallAction extends InstallInOtherServerAction { @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, ) { - super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService); + super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService, extensionGalleryService); } protected getInstallLabel(): string { @@ -694,8 +707,9 @@ export class WebInstallAction extends InstallInOtherServerAction { @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService, @IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService, + @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, ) { - super(`extensions.webInstall`, extensionManagementServerService.webExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService); + super(`extensions.webInstall`, extensionManagementServerService.webExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService, extensionGalleryService); } protected getInstallLabel(): string { -- cgit v1.2.3 From c7c0acd2ce4e0e541554fe8958922170a0fc53dc Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 12 Jul 2022 17:06:16 +0200 Subject: Git - Commit action button extension api (#154555) --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 9af7e3e8a77..56be4b4065f 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -2644,19 +2644,11 @@ export class SCMActionButton implements IDisposable { return; } - const executeButtonAction = async (commandId: string, ...args: any[]) => { - try { - await this.commandService.executeCommand(commandId, ...args); - } catch (ex) { - this.notificationService.error(ex); - } - }; - if (button.secondaryCommands?.length) { const actions: IAction[] = []; for (let index = 0; index < button.secondaryCommands.length; index++) { for (const command of button.secondaryCommands[index]) { - actions.push(new Action(command.id, command.title, undefined, true, async () => await executeButtonAction(command.id, ...(command.arguments || [])))); + actions.push(new Action(command.id, command.title, undefined, true, async () => await this.executeCommand(command.id, ...(command.arguments || [])))); } if (index !== button.secondaryCommands.length - 1) { actions.push(new Separator()); @@ -2682,7 +2674,7 @@ export class SCMActionButton implements IDisposable { this.button.enabled = button.enabled; this.button.label = button.command.title; - this.button.onDidClick(async () => await executeButtonAction(button.command.id, ...(button.command.arguments || [])), null, this.disposables.value); + this.button.onDidClick(async () => await this.executeCommand(button.command.id, ...(button.command.arguments || [])), null, this.disposables.value); this.disposables.value!.add(this.button); this.disposables.value!.add(attachButtonStyler(this.button, this.themeService)); @@ -2697,4 +2689,12 @@ export class SCMActionButton implements IDisposable { this.button = undefined; clearNode(this.container); } + + private async executeCommand(commandId: string, ...args: any[]): Promise { + try { + await this.commandService.executeCommand(commandId, ...args); + } catch (ex) { + this.notificationService.error(ex); + } + } } -- cgit v1.2.3 From 994ebb3488bdb8cc4c66dce6f6967356eb96f6ff Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 12 Jul 2022 10:16:36 -0700 Subject: Remove bracketed paste mode from sendText This has to be optionally applied to fix #153592 properly, probably with new API and command args. Fixes #154863 --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 6 ------ 1 file changed, 6 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 8a1810809f5..1f8a4970e50 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1487,12 +1487,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } async sendText(text: string, addNewLine: boolean): Promise { - // Apply bracketed paste sequences if the terminal has the mode enabled, this will prevent - // the text from triggering keybindings https://github.com/microsoft/vscode/issues/153592 - if (this.xterm?.raw.modes.bracketedPasteMode) { - text = `\x1b[200~${text}\x1b[201~`; - } - // Normalize line endings to 'enter' press. text = text.replace(/\r?\n/g, '\r'); if (addNewLine && text[text.length - 1] !== '\r') { -- cgit v1.2.3 From f86174be34131aaf3d118da10b3be5c59bae8375 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 12 Jul 2022 11:01:47 -0700 Subject: Export Interactive Window tab input (#154864) * Export Interactive Window tab input. * Update inputBoxUri. * remove inputBoxUri from API --- .../contrib/interactive/browser/interactiveEditorInput.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts index 17d8eaa03cf..b814db12b5b 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts @@ -8,7 +8,6 @@ import { IReference } from 'vs/base/common/lifecycle'; import * as paths from 'vs/base/common/path'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { IModelService } from 'vs/editor/common/services/model'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUntypedEditorInput } from 'vs/workbench/common/editor'; @@ -44,8 +43,10 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot return [this._notebookEditorInput]; } - override get resource() { - return this.primary.resource; + private _resource: URI; + + override get resource(): URI { + return this._resource; } private _inputResource: URI; @@ -71,7 +72,6 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot inputResource: URI, title: string | undefined, @IInstantiationService instantiationService: IInstantiationService, - @IModelService modelService: IModelService, @ITextModelService textModelService: ITextModelService, @IInteractiveDocumentService interactiveDocumentService: IInteractiveDocumentService, @@ -82,6 +82,7 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot this._notebookEditorInput = input; this._register(this._notebookEditorInput); this._initTitle = title; + this._resource = resource; this._inputResource = inputResource; this._inputResolver = null; this._editorModelReference = null; -- cgit v1.2.3 From 54927b0c5a5c2612876f2e71a6b038c4fac799e4 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 12 Jul 2022 12:11:00 -0700 Subject: Update notebook events comments (#154952) * Update notebook events comments * Update src/vs/workbench/contrib/notebook/browser/notebookEditor.ts Co-authored-by: Joyce Er Co-authored-by: Joyce Er --- src/vs/workbench/contrib/notebook/browser/notebookEditor.ts | 1 + src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts | 1 + 2 files changed, 2 insertions(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 367c1f80e41..99d1d8de1f0 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -244,6 +244,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti type WorkbenchNotebookOpenClassification = { owner: 'rebornix'; + comment: 'The notebook file open metrics. Used to get a better understanding of the performance of notebook file opening'; scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 6047d713272..dc1e9554840 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1100,6 +1100,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } type WorkbenchNotebookOpenClassification = { owner: 'rebornix'; + comment: 'Identify the notebook editor view type'; scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; -- cgit v1.2.3 From 66d8947f84b6dba315d577983ea1968c70718c74 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 12 Jul 2022 18:54:32 -0400 Subject: Fix #154963 (#154975) --- .../contrib/welcomeGettingStarted/browser/gettingStarted.ts | 5 +++-- .../contrib/welcomeGettingStarted/browser/media/gettingStarted.css | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index f119f79c69d..497dfb1165c 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -70,6 +70,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { restoreWalkthroughsConfigurationKey, RestoreWalkthroughsConfigurationValue } from 'vs/workbench/contrib/welcomeGettingStarted/browser/startupPage'; import { GettingStartedDetailsRenderer } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; const SLIDE_TRANSITION_TIME_MS = 250; const configurationKey = 'workbench.startupEditor'; @@ -968,7 +969,7 @@ export class GettingStartedPage extends EditorPane { if (category.isFeatured) { reset(featuredBadge, $('.featured', {}, $('span.featured-icon.codicon.codicon-star-empty'))); - reset(descriptionContent, category.description); + reset(descriptionContent, ...renderLabelWithIcons(category.description)); } return $('button.getting-started-category' + (category.isFeatured ? '.featured' : ''), @@ -1237,7 +1238,7 @@ export class GettingStartedPage extends EditorPane { this.iconWidgetFor(category), $('.category-description-container', {}, $('h2.category-title.max-lines-3', { 'x-category-title-for': category.id }, category.title), - $('.category-description.description.max-lines-3', { 'x-category-description-for': category.id }, category.description))); + $('.category-description.description.max-lines-3', { 'x-category-description-for': category.id }, ...renderLabelWithIcons(category.description)))); const stepListContainer = $('.step-list-container'); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css index eb269e524fe..60016371f6d 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css @@ -284,6 +284,11 @@ margin-left: 28px; } +.monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlide .getting-started-category .description-content > .codicon { + padding-right: 1px; + font-size: 16px; +} + .monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlide .getting-started-category .description-content:not(:empty){ margin-bottom: 8px; } @@ -368,7 +373,7 @@ flex: 150px 1 1000 } -.monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-category .codicon { +.monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlideDetails .gettingStartedDetailsContent>.getting-started-category>.codicon-getting-started-setup { margin-right: 8px; font-size: 28px; } -- cgit v1.2.3 From 18d44051cd7d0b15097c6c9afd9af16add88f56c Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 12 Jul 2022 17:01:20 -0700 Subject: Fix #151981. Avoid re-focus output when output already has focus. (#154991) --- .../contrib/notebook/browser/view/renderers/webviewPreloads.ts | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 0ce36982b24..f0eb46d08bb 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -400,6 +400,10 @@ async function webviewPreloads(ctx: PreloadContext) { function focusFirstFocusableInCell(cellId: string) { const cellOutputContainer = document.getElementById(cellId); if (cellOutputContainer) { + if (cellOutputContainer.contains(document.activeElement)) { + return; + } + const focusableElement = cellOutputContainer.querySelector('[tabindex="0"], [href], button, input, option, select, textarea') as HTMLElement | null; focusableElement?.focus(); } -- cgit v1.2.3 From 3e18c4995f9233937c391cf34057be191a989230 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Tue, 12 Jul 2022 17:05:28 -0700 Subject: Escape query, fixes #153583 (#154990) --- src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts index 8f833f178f6..2c59b8bfc44 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts @@ -554,7 +554,7 @@ export class SettingMatches { // Trim excess ending characters off the query. singleWordQuery = singleWordQuery.toLowerCase().replace(/[\s-\._]+$/, ''); lineToSearch = lineToSearch.toLowerCase(); - const singleWordRegex = new RegExp(`\\b${singleWordQuery}\\b`); + const singleWordRegex = new RegExp(`\\b${strings.escapeRegExpCharacters(singleWordQuery)}\\b`); if (singleWordRegex.test(lineToSearch)) { this.matchType |= SettingMatchType.WholeWordMatch; } -- cgit v1.2.3 From f64912465fa76c2c1d4f8cbe0152864c2194c281 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 12 Jul 2022 18:02:32 -0700 Subject: Add hot exit support for interactive window (#154974) * Export Interactive Window tab input. * Update inputBoxUri. * remove inputBoxUri from API * Hot exit * Expose inputBoxUri and make IW Tab ctor private * disable hot exit by default --- .../browser/interactive.contribution.ts | 43 +++++--- .../interactive/browser/interactiveCommon.ts | 5 + .../interactive/browser/interactiveEditor.ts | 12 ++- .../interactive/browser/interactiveEditorInput.ts | 120 ++++++++++++++++++++- .../contrib/notebook/common/notebookCommon.ts | 3 +- 5 files changed, 162 insertions(+), 21 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index c49ec5ad519..9969bd9c645 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -24,6 +24,7 @@ import { peekViewBorder /*, peekViewEditorBackground, peekViewResultsBackground import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/suggest'; import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { EditorActivation, IResourceEditorInput } from 'vs/platform/editor/common/editor'; @@ -38,12 +39,12 @@ import { contrastBorder, listInactiveSelectionBackground, registerColor, transpa import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { EditorExtensions, EditorsOrder, IEditorSerializer } from 'vs/workbench/common/editor'; +import { EditorExtensions, EditorsOrder, IEditorFactoryRegistry, IEditorSerializer } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; // import { Color } from 'vs/base/common/color'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; -import { INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon'; +import { InteractiveWindowSetting, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon'; import { IInteractiveDocumentService, InteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService'; import { InteractiveEditor } from 'vs/workbench/contrib/interactive/browser/interactiveEditor'; import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput'; @@ -52,7 +53,7 @@ import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/noteb import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { CellEditType, CellKind, CellUri, ICellOutput, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellKind, CellUri, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookContentProvider, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn'; @@ -194,7 +195,7 @@ export class InteractiveDocumentContribution extends Disposable implements IWork editorResolverService.registerEditor( `${Schemas.vscodeInteractiveInput}:/**`, { - id: InteractiveEditorInput.ID, + id: 'vscode-interactive-input', label: 'Interactive Editor', priority: RegisteredEditorPriority.exclusive }, @@ -211,7 +212,7 @@ export class InteractiveDocumentContribution extends Disposable implements IWork editorResolverService.registerEditor( `*.interactive`, { - id: InteractiveEditorInput.ID, + id: 'interactive', label: 'Interactive Editor', priority: RegisteredEditorPriority.exclusive }, @@ -272,8 +273,13 @@ workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveDocument workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveInputContentProvider, LifecyclePhase.Starting); export class InteractiveEditorSerializer implements IEditorSerializer { + public static readonly ID = InteractiveEditorInput.ID; + + constructor(@IConfigurationService private configurationService: IConfigurationService) { + } + canSerialize(): boolean { - return true; + return this.configurationService.getValue(InteractiveWindowSetting.interactiveWindowHotExit); } serialize(input: EditorInput): string { @@ -281,11 +287,16 @@ export class InteractiveEditorSerializer implements IEditorSerializer { return JSON.stringify({ resource: input.primary.resource, inputResource: input.inputResource, + name: input.getName(), + data: input.getSerialization() }); } deserialize(instantiationService: IInstantiationService, raw: string) { - type Data = { resource: URI; inputResource: URI }; + if (!this.canSerialize()) { + return undefined; + } + type Data = { resource: URI; inputResource: URI; data: any }; const data = parse(raw); if (!data) { return undefined; @@ -296,14 +307,15 @@ export class InteractiveEditorSerializer implements IEditorSerializer { } const input = InteractiveEditorInput.create(instantiationService, resource, inputResource); + input.restoreSerialization(data.data); return input; } } -// Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputSerializer( -// InteractiveEditorInput.ID, -// InteractiveEditorSerializer -// ); +Registry.as(EditorExtensions.EditorFactory) + .registerEditorSerializer( + InteractiveEditorSerializer.ID, + InteractiveEditorSerializer); registerSingleton(IInteractiveHistoryService, InteractiveHistoryService); registerSingleton(IInteractiveDocumentService, InteractiveDocumentService); @@ -738,15 +750,20 @@ registerThemingParticipant((theme) => { }); Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ - id: 'notebook', + id: 'interactiveWindow', order: 100, type: 'object', 'properties': { - [NotebookSetting.interactiveWindowAlwaysScrollOnNewCell]: { + [InteractiveWindowSetting.interactiveWindowAlwaysScrollOnNewCell]: { type: 'boolean', default: true, markdownDescription: localize('interactiveWindow.alwaysScrollOnNewCell', "Automatically scroll the interactive window to show the output of the last statement executed. If this value is false, the window will only scroll if the last cell was already the one scrolled to.") }, + [InteractiveWindowSetting.interactiveWindowHotExit]: { + type: 'boolean', + default: false, + markdownDescription: localize('interactiveWindow.hotExit', "Controls whether the interactive window sessions should be restored when the workspace reloads.") + } } }); diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts b/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts index ef37c64c93b..edda5c7f25f 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts @@ -6,3 +6,8 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const INTERACTIVE_INPUT_CURSOR_BOUNDARY = new RawContextKey<'none' | 'top' | 'bottom' | 'both'>('interactiveInputCursorAtBoundary', 'none'); + +export const InteractiveWindowSetting = { + interactiveWindowAlwaysScrollOnNewCell: 'interactiveWindow.alwaysScrollOnNewCell', + interactiveWindowHotExit: 'interactiveWindow.hotExit' +}; diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index ca1f164befa..81992e7547d 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -33,9 +33,8 @@ import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry' import { ILanguageService } from 'vs/editor/common/languages/language'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon'; +import { InteractiveWindowSetting, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon'; import { ComplexNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel'; -import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; @@ -57,6 +56,7 @@ import { ITextEditorOptions, TextEditorSelectionSource } from 'vs/platform/edito import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { NOTEBOOK_KERNEL } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; const DECORATION_KEY = 'interactiveInputDecoration'; const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState'; @@ -97,6 +97,7 @@ export class InteractiveEditor extends EditorPane { #contextMenuService: IContextMenuService; #editorGroupService: IEditorGroupsService; #notebookExecutionStateService: INotebookExecutionStateService; + #extensionService: IExtensionService; #widgetDisposableStore: DisposableStore = this._register(new DisposableStore()); #dimension?: DOM.Dimension; #notebookOptions: NotebookOptions; @@ -124,7 +125,8 @@ export class InteractiveEditor extends EditorPane { @IContextMenuService contextMenuService: IContextMenuService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, - @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService + @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService, + @IExtensionService extensionService: IExtensionService, ) { super( InteractiveEditor.ID, @@ -142,6 +144,7 @@ export class InteractiveEditor extends EditorPane { this.#contextMenuService = contextMenuService; this.#editorGroupService = editorGroupService; this.#notebookExecutionStateService = notebookExecutionStateService; + this.#extensionService = extensionService; this.#notebookOptions = new NotebookOptions(configurationService, notebookExecutionStateService, { cellToolbarInteraction: 'hover', globalToolbar: true, defaultCellCollapseConfig: { codeCell: { inputCollapsed: true } } }); this.#editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY); @@ -397,6 +400,7 @@ export class InteractiveEditor extends EditorPane { this.#notebookWidget.value?.setParentContextKeyService(this.#contextKeyService); const viewState = options?.viewState ?? this.#loadNotebookEditorViewState(input); + await this.#extensionService.whenInstalledExtensionsRegistered(); await this.#notebookWidget.value!.setModel(model.notebook, viewState?.notebook); model.notebook.setCellCollapseDefault(this.#notebookOptions.getCellCollapseDefault()); this.#notebookWidget.value!.setOptions({ @@ -528,7 +532,7 @@ export class InteractiveEditor extends EditorPane { const index = this.#notebookWidget.value!.getCellIndex(cvm); if (index === this.#notebookWidget.value!.getLength() - 1) { // If we're already at the bottom or auto scroll is enabled, scroll to the bottom - if (this.configurationService.getValue(NotebookSetting.interactiveWindowAlwaysScrollOnNewCell) || this.#cellAtBottom(cvm)) { + if (this.configurationService.getValue(InteractiveWindowSetting.interactiveWindowAlwaysScrollOnNewCell) || this.#cellAtBottom(cvm)) { this.#notebookWidget.value!.scrollToBottom(); } } diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts index b814db12b5b..71b125ec799 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { VSBuffer } from 'vs/base/common/buffer'; import { Event } from 'vs/base/common/event'; import { IReference } from 'vs/base/common/lifecycle'; import * as paths from 'vs/base/common/path'; @@ -14,7 +15,9 @@ import { IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IInteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService'; import { IInteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService'; -import { IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; +import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; +import { CellKind, ICellDto2, IOutputDto, IResolvedNotebookEditorModel, NotebookCellCollapseState, NotebookCellInternalMetadata, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; export class InteractiveEditorInput extends EditorInput implements ICompositeNotebookEditorInput { @@ -131,7 +134,14 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot return this._inputResolver; } - this._inputResolver = this._resolveEditorModel(); + this._inputResolver = this._resolveEditorModel().then(editorModel => { + if (this._data) { + editorModel?.notebook.reset(this._data.notebookData.cells.map((cell: ISerializedCell) => deserializeCell(cell)), this._data.notebookData.metadata, this._data.notebookData.transientOptions); + } + + return editorModel; + }); + return this._inputResolver; } @@ -143,6 +153,10 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot this._interactiveDocumentService.willCreateInteractiveDocument(this.resource!, this.inputResource, language); this._inputModelRef = await this._textModelService.createModelReference(this.inputResource); + if (this._data && this._data.inputData) { + this._inputModelRef.object.textEditorModel.setValue(this._data.inputData.value); + } + return this._inputModelRef.object.textEditorModel; } @@ -167,6 +181,37 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot return basename.substr(0, basename.length - paths.extname(p).length); } + getSerialization(): { notebookData: any | undefined; inputData: any | undefined } { + return { + notebookData: this._serializeNotebook(this._editorModelReference?.notebook), + inputData: this._inputModelRef ? { + value: this._inputModelRef.object.textEditorModel.getValue(), + language: this._inputModelRef.object.textEditorModel.getLanguageId() + } : undefined + }; + } + + private _data: { notebookData: any | undefined; inputData: any | undefined } | undefined; + + async restoreSerialization(data: { notebookData: any | undefined; inputData: any | undefined } | undefined) { + this._data = data; + } + + private _serializeNotebook(notebook?: NotebookTextModel) { + if (!notebook) { + return undefined; + } + + const cells = notebook.cells.map(cell => serializeCell(cell)); + + return { + cells: cells, + metadata: notebook.metadata, + transientOptions: notebook.transientOptions + }; + } + + override dispose() { // we support closing the interactive window without prompt, so the editor model should not be dirty this._editorModelReference?.revert({ soft: true }); @@ -184,3 +229,74 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot return this._historyService; } } + +/** + * Serialization of interactive notebook. + * This is not placed in notebook land as regular notebooks are handled by file service directly. + */ + +interface ISerializedOutputItem { + readonly mime: string; + readonly data: number[]; +} + +interface ISerializedCellOutput { + outputs: ISerializedOutputItem[]; + metadata?: Record; + outputId: string; +} + +export interface ISerializedCell { + source: string; + language: string; + mime: string | undefined; + cellKind: CellKind; + outputs: ISerializedCellOutput[]; + metadata?: NotebookCellMetadata; + internalMetadata?: NotebookCellInternalMetadata; + collapseState?: NotebookCellCollapseState; +} + +function serializeCell(cell: NotebookCellTextModel): ISerializedCell { + return { + cellKind: cell.cellKind, + language: cell.language, + metadata: cell.metadata, + mime: cell.mime, + outputs: cell.outputs.map(output => serializeCellOutput(output)), + source: cell.getValue() + }; +} + +function deserializeCell(cell: ISerializedCell): ICellDto2 { + return { + cellKind: cell.cellKind, + source: cell.source, + language: cell.language, + metadata: cell.metadata, + mime: cell.mime, + outputs: cell.outputs.map((output) => deserializeCellOutput(output)) + }; +} + +function serializeCellOutput(output: IOutputDto): ISerializedCellOutput { + return { + outputId: output.outputId, + outputs: output.outputs.map(ot => ({ + mime: ot.mime, + data: ot.data.buffer ? Array.from(ot.data.buffer) : [] + })), + metadata: output.metadata + }; +} + +function deserializeCellOutput(output: ISerializedCellOutput): IOutputDto { + return { + outputId: output.outputId, + outputs: output.outputs.map(ot => ({ + mime: ot.mime, + data: VSBuffer.fromByteArray(ot.data) + })), + metadata: output.metadata + }; +} diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index a95ecc6fb84..8e13cf654cd 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -923,8 +923,7 @@ export const NotebookSetting = { interactiveWindowCollapseCodeCells: 'interactiveWindow.collapseCellInputCode', outputLineHeight: 'notebook.outputLineHeight', outputFontSize: 'notebook.outputFontSize', - outputFontFamily: 'notebook.outputFontFamily', - interactiveWindowAlwaysScrollOnNewCell: 'interactiveWindow.alwaysScrollOnNewCell' + outputFontFamily: 'notebook.outputFontFamily' } as const; export const enum CellStatusbarAlignment { -- cgit v1.2.3 From 052d5b0027f6c9d64c8ca35955cb5117ba94d5d7 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Tue, 12 Jul 2022 18:03:49 -0700 Subject: Fix issue with kernel preselection being overridden by view state (#154968) * Fix view state overriding selected kernel * Add test to verify correct kernel is used --- src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index dc1e9554840..3debc2599a6 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1693,8 +1693,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private _restoreSelectedKernel(viewState: INotebookEditorViewState | undefined): void { if (viewState?.selectedKernelId && this.textModel) { - const kernel = this.notebookKernelService.getMatchingKernel(this.textModel).all.find(k => k.id === viewState.selectedKernelId); - if (kernel) { + const matching = this.notebookKernelService.getMatchingKernel(this.textModel); + const kernel = matching.all.find(k => k.id === viewState.selectedKernelId); + // Selected kernel may have already been picked prior to the view state loading + // If so, don't overwrite it with the saved kernel. + if (kernel && !matching.selected) { this.notebookKernelService.selectKernelForNotebook(kernel, this.textModel); } } -- cgit v1.2.3