diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index e2ed5c83b3f08..2ff18c80e4861 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -18,6 +18,7 @@ import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/termi import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; import { ITerminalCapabilityStore } from 'vs/workbench/contrib/terminal/common/capabilities/capabilities'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; export const ITerminalService = createDecorator('terminalService'); export const ITerminalEditorService = createDecorator('terminalEditorService'); @@ -417,7 +418,7 @@ export interface ITerminalInstance { readonly processName: string; readonly sequence?: string; readonly staticTitle?: string; - readonly workspaceFolder?: string; + readonly workspaceFolder?: IWorkspaceFolder; readonly cwd?: string; readonly initialCwd?: string; readonly capabilities: ITerminalCapabilityStore; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 8f9f89d5ab6a0..2cec5558e7602 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -52,7 +52,7 @@ import { DataTransfers } from 'vs/base/browser/dnd'; import { CodeDataTransfers, containsDragType, DragAndDropObserver, IDragAndDropObserverCallbacks } from 'vs/workbench/browser/dnd'; import { getColorClass, getColorStyleElement, getStandardColors } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { getTerminalResourcesFromDragEvent, getTerminalUri } from 'vs/workbench/contrib/terminal/browser/terminalUri'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -76,6 +76,7 @@ import { ITextModelContentProvider, ITextModelService } from 'vs/editor/common/s import { IDialogService, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { Schemas } from 'vs/base/common/network'; +import { withNullAsUndefined } from 'vs/base/common/types'; const enum Constants { /** @@ -196,7 +197,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _processName: string = ''; private _sequence?: string; private _staticTitle?: string; - private _workspaceFolder?: string; + private _workspaceFolder?: IWorkspaceFolder; private _labelComputer?: TerminalLabelComputer; private _userHome?: string; private _hasScrollBar?: boolean; @@ -271,7 +272,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { get processName(): string { return this._processName; } get sequence(): string | undefined { return this._sequence; } get staticTitle(): string | undefined { return this._staticTitle; } - get workspaceFolder(): string | undefined { return this._workspaceFolder; } + get workspaceFolder(): IWorkspaceFolder | undefined { return this._workspaceFolder; } get cwd(): string | undefined { return this._cwd; } get initialCwd(): string | undefined { return this._initialCwd; } get description(): string | undefined { @@ -375,6 +376,20 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // the resource is already set when it's been moved from another window this._resource = resource || getTerminalUri(this._workspaceContextService.getWorkspace().id, this.instanceId, this.title); + if (this.shellLaunchConfig.cwd) { + const cwdUri = typeof this._shellLaunchConfig.cwd === 'string' ? URI.from({ + scheme: Schemas.file, + path: this._shellLaunchConfig.cwd + }) : this._shellLaunchConfig.cwd; + if (cwdUri) { + this._workspaceFolder = withNullAsUndefined(this._workspaceContextService.getWorkspaceFolder(cwdUri)); + } + } + if (!this._workspaceFolder) { + const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); + this._workspaceFolder = activeWorkspaceRootUri ? withNullAsUndefined(this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined; + } + this._terminalHasTextContextKey = TerminalContextKeys.textSelected.bindTo(this._contextKeyService); this._terminalA11yTreeFocusContextKey = TerminalContextKeys.a11yTreeFocus.bindTo(this._contextKeyService); this._terminalAltBufferActiveContextKey = TerminalContextKeys.altBufferActive.bindTo(this._contextKeyService); @@ -989,7 +1004,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._widgetManager.attachToElement(xterm.raw.element); this._processManager.onProcessReady((e) => { this._linkManager?.setWidgetManager(this._widgetManager); - this._workspaceFolder = path.basename(e.cwd.toString()); }); // const computedStyle = window.getComputedStyle(this._container); @@ -2336,7 +2350,7 @@ export class TerminalLabelComputer extends Disposable { const templateProperties: ITerminalLabelTemplateProperties = { cwd: this._instance.cwd || this._instance.initialCwd || '', cwdFolder: '', - workspaceFolder: this._instance.workspaceFolder, + workspaceFolder: this._instance.workspaceFolder ? path.basename(this._instance.workspaceFolder.uri.fsPath) : undefined, local: this._instance.shellLaunchConfig.type === 'Local' ? this._instance.shellLaunchConfig.type : undefined, process: this._instance.processName, sequence: this._instance.sequence, @@ -2355,11 +2369,16 @@ export class TerminalLabelComputer extends Disposable { } const detection = this._instance.capabilities.has(TerminalCapability.CwdDetection) || this._instance.capabilities.has(TerminalCapability.NaiveCwdDetection); const folders = this._workspaceContextService.getWorkspace().folders; - const zeroRootWorkspace = folders.length === 0 && this.pathsEqual(templateProperties.cwd, this._instance.userHome || this._configHelper.config.cwd); - const singleRootWorkspace = folders.length === 1 && this.pathsEqual(templateProperties.cwd, this._configHelper.config.cwd || folders[0]?.uri.fsPath); const multiRootWorkspace = folders.length > 1; - if (this._instance.cwd !== this._instance.initialCwd || multiRootWorkspace) { - templateProperties.cwdFolder = (!templateProperties.cwd || !detection || zeroRootWorkspace || singleRootWorkspace) ? '' : path.basename(templateProperties.cwd); + + // Only set cwdFolder if detection is on + if (templateProperties.cwd && detection) { + const cwdUri = URI.from({ scheme: this._instance.workspaceFolder?.uri.scheme || Schemas.file, path: this._instance.cwd }); + // Multi-root workspaces always show cwdFolder to disambiguate them, otherwise only show + // when it differs from the workspace folder in which it was launched from + if (multiRootWorkspace || cwdUri.fsPath !== this._instance.workspaceFolder?.uri.fsPath) { + templateProperties.cwdFolder = path.basename(templateProperties.cwd); + } } //Remove special characters that could mess with rendering diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts index fc1f3eaef5863..154ae496004b3 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalInstance.test.ts @@ -6,7 +6,7 @@ import { deepStrictEqual, strictEqual } from 'assert'; import { isWindows } from 'vs/base/common/platform'; import { TerminalLabelComputer, parseExitResult } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; -import { IWorkspaceContextService, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, IWorkspaceFolder, toWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Workspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -16,8 +16,10 @@ import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/term import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ProcessState } from 'vs/workbench/contrib/terminal/common/terminal'; import { basename } from 'vs/base/common/path'; +import { URI } from 'vs/base/common/uri'; import { TerminalCapabilityStore } from 'vs/workbench/contrib/terminal/common/capabilities/terminalCapabilityStore'; import { TerminalCapability } from 'vs/workbench/contrib/terminal/common/capabilities/capabilities'; +import { Schemas } from 'vs/base/common/network'; function createInstance(partial?: Partial): Pick { const capabilities = new TerminalCapabilityStore(); @@ -225,7 +227,7 @@ suite('Workbench - TerminalInstance', () => { test('should resolve workspaceFolder', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' - ', title: '${workspaceFolder}', description: '${workspaceFolder}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'zsh', workspaceFolder: 'folder' }), mockContextService); + terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'zsh', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: 'folder' }) } as IWorkspaceFolder }), mockContextService); terminalLabelComputer.refreshLabel(); strictEqual(terminalLabelComputer.title, 'folder'); strictEqual(terminalLabelComputer.description, 'folder'); @@ -273,7 +275,7 @@ suite('Workbench - TerminalInstance', () => { test('should always return static title when specified', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}', description: '${workspaceFolder}' } } } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: 'folder', staticTitle: 'my-title' }), mockContextService); + terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: 'folder' }) } as IWorkspaceFolder, staticTitle: 'my-title' }), mockContextService); terminalLabelComputer.refreshLabel(); strictEqual(terminalLabelComputer.title, 'my-title'); strictEqual(terminalLabelComputer.description, 'folder'); @@ -281,7 +283,7 @@ suite('Workbench - TerminalInstance', () => { test('should provide cwdFolder for all cwds only when in multi-root', () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}${separator}${cwdFolder}', description: '${cwdFolder}' } }, cwd: ROOT_1 } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: 'folder', cwd: ROOT_1 }), mockContextService); + terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: 'folder' }) } as IWorkspaceFolder, cwd: ROOT_1 }), mockContextService); terminalLabelComputer.refreshLabel(); // single-root, cwd is same as root strictEqual(terminalLabelComputer.title, 'process'); @@ -289,7 +291,7 @@ suite('Workbench - TerminalInstance', () => { // multi-root configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}${separator}${cwdFolder}', description: '${cwdFolder}' } }, cwd: ROOT_1 } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: 'folder', cwd: ROOT_2 }), mockMultiRootContextService); + terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: 'folder' }) } as IWorkspaceFolder, cwd: ROOT_2 }), mockMultiRootContextService); terminalLabelComputer.refreshLabel(); if (isWindows) { strictEqual(terminalLabelComputer.title, 'process'); @@ -302,12 +304,12 @@ suite('Workbench - TerminalInstance', () => { test('should hide cwdFolder in single folder workspaces when cwd matches the workspace\'s default cwd even when slashes differ', async () => { configurationService = new TestConfigurationService({ terminal: { integrated: { tabs: { separator: ' ~ ', title: '${process}${separator}${cwdFolder}', description: '${cwdFolder}' } }, cwd: '\\foo\\root1' } }); configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!); - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: 'folder', cwd: ROOT_1 }), mockContextService); + terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: 'folder' }) } as IWorkspaceFolder, cwd: ROOT_1 }), mockContextService); terminalLabelComputer.refreshLabel(); strictEqual(terminalLabelComputer.title, 'process'); strictEqual(terminalLabelComputer.description, ''); if (!isWindows) { - terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: 'folder', cwd: ROOT_2 }), mockContextService); + terminalLabelComputer = new TerminalLabelComputer(configHelper, createInstance({ capabilities, processName: 'process', workspaceFolder: { uri: URI.from({ scheme: Schemas.file, path: 'folder' }) } as IWorkspaceFolder, cwd: ROOT_2 }), mockContextService); terminalLabelComputer.refreshLabel(); strictEqual(terminalLabelComputer.title, 'process ~ root2'); strictEqual(terminalLabelComputer.description, 'root2');