From 47b27ab160e91675511afc9620ad41725a0e3f94 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 7 Jan 2025 12:40:06 -0600 Subject: [PATCH 1/6] wip #234672, force shell integration change for reconnected --- .../api/browser/mainThreadTerminalShellIntegration.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts b/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts index 6181cd4c637..bc204e19a06 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts @@ -41,6 +41,11 @@ export class MainThreadTerminalShellIntegration extends Disposable implements Ma }), () => instance ); })).event; + for (const terminal of this._terminalService.instances.filter(i => !!i.shellLaunchConfig.attachPersistentProcess)) { + if (terminal.capabilities.has(TerminalCapability.CommandDetection) || terminal.capabilities.has(TerminalCapability.CwdDetection)) { + this._proxy.$shellIntegrationChange(terminal.instanceId); + } + } this._store.add(onDidAddCommandDetection(e => this._proxy.$shellIntegrationChange(e.instanceId))); // onDidStartTerminalShellExecution From 2e1cc3848f414cd60cec3bc58e81ad9ff40e38b8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Jan 2025 12:59:02 -0800 Subject: [PATCH 2/6] Initialize shellIntegration.cwd Fixes #234672 --- .../mainThreadTerminalShellIntegration.ts | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts b/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts index bc204e19a06..e2ee50cc3a4 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalShellIntegration.ts @@ -33,7 +33,18 @@ export class MainThreadTerminalShellIntegration extends Disposable implements Ma } })); - // onDidChangeTerminalShellIntegration + // onDidchangeTerminalShellIntegration initial state + for (const terminal of this._terminalService.instances) { + if (terminal.capabilities.has(TerminalCapability.CommandDetection)) { + this._proxy.$shellIntegrationChange(terminal.instanceId); + const cwdDetection = terminal.capabilities.get(TerminalCapability.CwdDetection); + if (cwdDetection) { + this._proxy.$cwdChange(terminal.instanceId, this._convertCwdToUri(cwdDetection.getCwd())); + } + } + } + + // onDidChangeTerminalShellIntegration via command detection const onDidAddCommandDetection = this._store.add(this._terminalService.createOnInstanceEvent(instance => { return Event.map( Event.filter(instance.capabilities.onDidAddCapabilityType, e => { @@ -41,13 +52,14 @@ export class MainThreadTerminalShellIntegration extends Disposable implements Ma }), () => instance ); })).event; - for (const terminal of this._terminalService.instances.filter(i => !!i.shellLaunchConfig.attachPersistentProcess)) { - if (terminal.capabilities.has(TerminalCapability.CommandDetection) || terminal.capabilities.has(TerminalCapability.CwdDetection)) { - this._proxy.$shellIntegrationChange(terminal.instanceId); - } - } this._store.add(onDidAddCommandDetection(e => this._proxy.$shellIntegrationChange(e.instanceId))); + // onDidChangeTerminalShellIntegration via cwd + const cwdChangeEvent = this._store.add(this._terminalService.createOnInstanceCapabilityEvent(TerminalCapability.CwdDetection, e => e.onDidChangeCwd)); + this._store.add(cwdChangeEvent.event(e => { + this._proxy.$cwdChange(e.instance.instanceId, this._convertCwdToUri(e.data)); + })); + // onDidStartTerminalShellExecution const commandDetectionStartEvent = this._store.add(this._terminalService.createOnInstanceCapabilityEvent(TerminalCapability.CommandDetection, e => e.onCommandExecuted)); let currentCommand: ITerminalCommand | undefined; @@ -80,12 +92,6 @@ export class MainThreadTerminalShellIntegration extends Disposable implements Ma }); })); - // onDidChangeTerminalShellIntegration via cwd - const cwdChangeEvent = this._store.add(this._terminalService.createOnInstanceCapabilityEvent(TerminalCapability.CwdDetection, e => e.onDidChangeCwd)); - this._store.add(cwdChangeEvent.event(e => { - this._proxy.$cwdChange(e.instance.instanceId, this._convertCwdToUri(e.data)); - })); - // Clean up after dispose this._store.add(this._terminalService.onDidDisposeInstance(e => this._proxy.$closeTerminal(e.instanceId))); } From e03d4a0ea2e833d3db053b19670e0e44145f8212 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 8 Jan 2025 22:05:04 +0100 Subject: [PATCH 3/6] Git - Add "Open on GitHub" command to timeline hover (#237523) --- extensions/git/src/timelineProvider.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index 2b3da08f82e..fbf9b139a0a 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, ConfigurationChangeEvent, Disposable, env, Event, EventEmitter, MarkdownString, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace, l10n } from 'vscode'; +import { CancellationToken, ConfigurationChangeEvent, Disposable, env, Event, EventEmitter, MarkdownString, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace, l10n, Command } from 'vscode'; import { Model } from './model'; import { Repository, Resource } from './repository'; import { debounce } from './decorators'; @@ -12,6 +12,7 @@ import { CommandCenter } from './commands'; import { OperationKind, OperationResult } from './operation'; import { getCommitShortHash } from './util'; import { CommitShortStat } from './git'; +import { getRemoteSourceControlHistoryItemCommands } from './remoteSource'; export class GitTimelineItem extends TimelineItem { static is(item: TimelineItem): item is GitTimelineItem { @@ -50,7 +51,7 @@ export class GitTimelineItem extends TimelineItem { return this.shortenRef(this.previousRef); } - setItemDetails(uri: Uri, hash: string | undefined, author: string, email: string | undefined, date: string, message: string, shortStat?: CommitShortStat): void { + setItemDetails(uri: Uri, hash: string | undefined, author: string, email: string | undefined, date: string, message: string, shortStat?: CommitShortStat, remoteSourceCommands: Command[] = []): void { this.tooltip = new MarkdownString('', true); this.tooltip.isTrusted = true; this.tooltip.supportHtml = true; @@ -89,6 +90,15 @@ export class GitTimelineItem extends TimelineItem { this.tooltip.appendMarkdown(`[\`$(git-commit) ${getCommitShortHash(uri, hash)} \`](command:git.viewCommit?${encodeURIComponent(JSON.stringify([uri, hash]))} "${l10n.t('View Commit')}")`); this.tooltip.appendMarkdown(' '); this.tooltip.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(hash))} "${l10n.t('Copy Commit Hash')}")`); + + // Remote commands + if (remoteSourceCommands.length > 0) { + this.tooltip.appendMarkdown('  |  '); + + const remoteCommandsMarkdown = remoteSourceCommands + .map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify([...command.arguments ?? [], hash]))} "${command.tooltip}")`); + this.tooltip.appendMarkdown(remoteCommandsMarkdown.join(' ')); + } } } @@ -204,6 +214,11 @@ export class GitTimelineProvider implements TimelineProvider { const openComparison = l10n.t('Open Comparison'); + const defaultRemote = repo.getDefaultRemote(); + const remoteSourceCommands: Command[] = defaultRemote?.fetchUrl + ? await getRemoteSourceControlHistoryItemCommands(defaultRemote.fetchUrl) + : []; + const items = commits.map((c, i) => { const date = dateType === 'authored' ? c.authorDate : c.commitDate; @@ -215,7 +230,7 @@ export class GitTimelineProvider implements TimelineProvider { item.description = c.authorName; } - item.setItemDetails(uri, c.hash, c.authorName!, c.authorEmail, dateFormatter.format(date), message, c.shortStat); + item.setItemDetails(uri, c.hash, c.authorName!, c.authorEmail, dateFormatter.format(date), message, c.shortStat, remoteSourceCommands); const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri); if (cmd) { From 1b8c51694938575c794ac9754205a8fab04fe691 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 8 Jan 2025 13:08:23 -0800 Subject: [PATCH 4/6] Fix terminal chat placeholder Fixes microsoft/vscode-copilot#10440 --- .../terminalContrib/chat/browser/terminalChatWidget.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts index d1a310aae29..9077da3811f 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget.ts @@ -328,6 +328,12 @@ export class TerminalChatWidget extends Disposable { const model = this._model.value; if (model) { this._inlineChatWidget.setChatModel(model, this._loadViewState()); + model.waitForInitialization().then(() => { + if (token.isCancellationRequested) { + return; + } + this._resetPlaceholder(); + }); } if (!this._model.value) { throw new Error('Failed to start chat session'); From e6cba379a4041fd8705e7fec052a8ea6960efb54 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 8 Jan 2025 14:55:18 -0800 Subject: [PATCH 5/6] Try asking for different claims in another request (#237531) I'm hoping that this solution will be good enough until we stop depending on the id token for certain things. Fixes https://github.com/microsoft/vscode/issues/237370 --- .../src/node/cachedPublicClientApplication.ts | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts b/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts index 7ebf7a1630c..a986b217983 100644 --- a/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts +++ b/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts @@ -93,6 +93,7 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] got result`); // Check expiration of id token and if it's 5min before expiration, force a refresh. // this is what MSAL does for access tokens already so we're just adding it for id tokens since we care about those. + // NOTE: Once we stop depending on id tokens for some things we can remove all of this. const idTokenExpirationInSecs = (result.idTokenClaims as { exp?: number }).exp; if (idTokenExpirationInSecs) { const fiveMinutesBefore = new Date( @@ -106,12 +107,35 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica ? { ...request, claims: '{ "id_token": {}}' } : { ...request, forceRefresh: true }; result = await this._sequencer.queue(() => this._pca.acquireTokenSilent(newRequest)); - this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] got refreshed result`); + this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] got forced result`); } const newIdTokenExpirationInSecs = (result.idTokenClaims as { exp?: number }).exp; if (newIdTokenExpirationInSecs) { - if (new Date(newIdTokenExpirationInSecs * 1000) < new Date()) { + const fiveMinutesBefore = new Date( + (newIdTokenExpirationInSecs - 5 * 60) // subtract 5 minutes + * 1000 // convert to milliseconds + ); + if (fiveMinutesBefore < new Date()) { this._logger.error(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] id token is still expired.`); + + // HACK: Only for the Broker we try one more time with different claims to force a refresh. Why? We've seen the Broker caching tokens by the claims requested, thus + // there has been a situation where both tokens are expired. + if (this._isBrokerAvailable) { + this._logger.error(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] forcing refresh with different claims...`); + const newRequest = { ...request, claims: '{ "access_token": {}}' }; + result = await this._sequencer.queue(() => this._pca.acquireTokenSilent(newRequest)); + this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] got forced result with different claims`); + const newIdTokenExpirationInSecs = (result.idTokenClaims as { exp?: number }).exp; + if (newIdTokenExpirationInSecs) { + const fiveMinutesBefore = new Date( + (newIdTokenExpirationInSecs - 5 * 60) // subtract 5 minutes + * 1000 // convert to milliseconds + ); + if (fiveMinutesBefore < new Date()) { + this._logger.error(`[acquireTokenSilent] [${this._clientId}] [${this._authority}] [${request.scopes.join(' ')}] [${request.account.username}] id token is still expired.`); + } + } + } } } } From a016c0be118543e874170db95ca7401aec8ce185 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 9 Jan 2025 00:37:44 +0100 Subject: [PATCH 6/6] Support show range for inline edits (#237532) Support a show range around the edit --- src/vs/editor/common/config/editorOptions.ts | 8 -------- src/vs/editor/common/languages.ts | 3 +++ .../browser/model/inlineCompletionsModel.ts | 9 +++++---- .../browser/model/inlineEditsAdapter.ts | 1 + .../browser/model/provideInlineCompletions.ts | 3 +++ src/vs/monaco.d.ts | 3 ++- .../workbench/api/common/extHostLanguageFeatures.ts | 1 + src/vscode-dts/vscode.proposed.inlineEdit.d.ts | 12 +++++++++--- 8 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 2bd9dfd9e2e..908dcac26bd 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -4200,7 +4200,6 @@ export interface IInlineSuggestOptions { useWordInsertionView?: 'never' | 'whenPossible'; useWordReplacementView?: 'never' | 'whenPossible'; - onlyShowWhenCloseToCursor?: boolean; useGutterIndicator?: boolean; }; }; @@ -4235,7 +4234,6 @@ class InlineEditorSuggest extends BaseEditorOption { @@ -2396,6 +2398,7 @@ export interface MappedEditsProvider { export interface IInlineEdit { text: string; range: IRange; + showRange?: IRange; accepted?: Command; rejected?: Command; shown?: Command; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index b77a2c0df81..efb2b8c4bc0 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -54,7 +54,6 @@ export class InlineCompletionsModel extends Disposable { private readonly _editorObs = observableCodeEditor(this._editor); - private readonly _onlyShowWhenCloseToCursor = this._editorObs.getOption(EditorOption.inlineSuggest).map(v => !!v.edits.experimental.onlyShowWhenCloseToCursor); private readonly _suggestPreviewEnabled = this._editorObs.getOption(EditorOption.suggest).map(v => v.preview); private readonly _suggestPreviewMode = this._editorObs.getOption(EditorOption.suggest).map(v => v.previewMode); private readonly _inlineSuggestMode = this._editorObs.getOption(EditorOption.inlineSuggest).map(v => v.mode); @@ -358,12 +357,13 @@ export class InlineCompletionsModel extends Disposable { const cursorPos = this.primaryPosition.read(reader); const cursorAtInlineEdit = LineRange.fromRangeInclusive(edit.range).addMargin(1, 1).contains(cursorPos.lineNumber); + const cursorInsideShowRange = cursorAtInlineEdit || (item.inlineEdit.inlineCompletion.cursorShowRange?.containsPosition(cursorPos) ?? true); - const cursorDist = LineRange.fromRange(edit.range).distanceToLine(this.primaryPosition.read(reader).lineNumber); - - if (this._onlyShowWhenCloseToCursor.read(reader) && cursorDist > 3 && !item.inlineEdit.request.isExplicitRequest && !this._inAcceptFlow.read(reader)) { + if (!cursorInsideShowRange && !this._inAcceptFlow.read(reader)) { return undefined; } + + const cursorDist = LineRange.fromRange(edit.range).distanceToLine(this.primaryPosition.read(reader).lineNumber); const disableCollapsing = true; const currentItemIsCollapsed = !disableCollapsing && (cursorDist > 1 && this._collapsedInlineEditId.read(reader) === item.inlineEdit.semanticId); @@ -575,6 +575,7 @@ export class InlineCompletionsModel extends Disposable { editor.pushUndoStop(); if (completion.snippetInfo) { + // ... editor.executeEdits( 'inlineSuggestion.accept', [ diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEditsAdapter.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEditsAdapter.ts index 8d651a32a85..c2ba76a7456 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEditsAdapter.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEditsAdapter.ts @@ -69,6 +69,7 @@ export class InlineEditsAdapter extends Disposable { items: definedEdits.map(e => { return { range: e.result.range, + showRange: e.result.showRange, insertText: e.result.text, command: e.result.accepted, shownCommand: e.result.shown, diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts index a240f67595d..d6b357f0b14 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts @@ -330,6 +330,7 @@ export class InlineCompletionItem { range, insertText, snippetInfo, + Range.lift(inlineCompletion.showRange) ?? undefined, inlineCompletion.additionalTextEdits || getReadonlyEmptyArray(), inlineCompletion, source, @@ -345,6 +346,7 @@ export class InlineCompletionItem { readonly range: Range, readonly insertText: string, readonly snippetInfo: SnippetInfo | undefined, + readonly cursorShowRange: Range | undefined, readonly additionalTextEdits: readonly ISingleEditOperation[], @@ -380,6 +382,7 @@ export class InlineCompletionItem { updatedRange, this.insertText, this.snippetInfo, + this.cursorShowRange, this.additionalTextEdits, this.sourceInlineCompletion, this.source, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index c1df5f99ffe..bd27d479941 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4607,7 +4607,6 @@ declare namespace monaco.editor { useInterleavedLinesDiff?: 'never' | 'always' | 'afterJump'; useWordInsertionView?: 'never' | 'whenPossible'; useWordReplacementView?: 'never' | 'whenPossible'; - onlyShowWhenCloseToCursor?: boolean; useGutterIndicator?: boolean; }; }; @@ -7277,6 +7276,7 @@ declare namespace monaco.languages { */ readonly completeBracketPairs?: boolean; readonly isInlineEdit?: boolean; + readonly showRange?: IRange; } export interface InlineCompletions { @@ -8164,6 +8164,7 @@ declare namespace monaco.languages { export interface IInlineEdit { text: string; range: IRange; + showRange?: IRange; accepted?: Command; rejected?: Command; shown?: Command; diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 53a5c89264e..d5ef857b79f 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1577,6 +1577,7 @@ class InlineEditAdapter { pid, text: result.text, range: typeConvert.Range.from(result.range), + showRange: typeConvert.Range.from(result.showRange), accepted: acceptCommand, rejected: rejectCommand, shown: shownCommand, diff --git a/src/vscode-dts/vscode.proposed.inlineEdit.d.ts b/src/vscode-dts/vscode.proposed.inlineEdit.d.ts index 9a5eabb9f8b..21fd5e34c01 100644 --- a/src/vscode-dts/vscode.proposed.inlineEdit.d.ts +++ b/src/vscode-dts/vscode.proposed.inlineEdit.d.ts @@ -13,11 +13,17 @@ declare module 'vscode' { readonly text: string; /** - * An range that will be replaced by the text of the inline edit. - * If change is only additive, this can be empty (same start and end position). + * A range that will be replaced by the text of the inline edit. + * If the change is only additive, this can be empty (same start and end position). */ readonly range: Range; + /** + * A range specifying when the edit can be shown based on the cursor position. + * If the cursor is within this range, the inline edit can be displayed. + */ + readonly showRange?: Range; + /** * An optional command that will be executed after applying the inline edit. */ @@ -36,7 +42,7 @@ declare module 'vscode' { * Creates a new inline edit. * * @param text The new text for this edit. - * @param replaceRange An range that will be replaced by the text of the inline edit. + * @param range A range that will be replaced by the text of the inline edit. */ constructor(text: string, range: Range); }