From 90a6ac4a63b9ae55f3cf79c7744cd64f35332c29 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 23 May 2023 15:25:53 +0200 Subject: [PATCH 01/65] adding a console log --- .../src/languageFeatures/quickFix.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 515402f6e1e..0d87cb86eac 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -216,6 +216,8 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider Date: Tue, 23 May 2023 16:17:41 +0200 Subject: [PATCH 02/65] initial code --- .../src/languageFeatures/quickFix.ts | 42 ++++++++++++++++++- .../src/languageFeatures/util/codeAction.ts | 1 + .../browser/interactiveEditorActions.ts | 2 + 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 0d87cb86eac..b83af8d2526 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -45,12 +45,33 @@ class ApplyCodeActionCommand implements Command { ] } */ + console.log('resource : ', resource); + console.log('action : ', action); + console.log('diagnostic : ', diagnostic); + this.telemetryReporter.logTelemetry('quickFix.execute', { fixName: action.fixName }); this.diagnosticManager.deleteDiagnostic(resource, diagnostic); - return applyCodeActionCommands(this.client, action.commands, nulToken); + // TODO: point of interest + const codeActionResult = await applyCodeActionCommands(this.client, action.commands, nulToken); + + // TODO: how to find the end line number of the class? + // const startLine = diagnostic.range.start.line; + // Find end line using the outline + // vscode.window.activeTextEditor?.document + + const numberLines = vscode.window.activeTextEditor?.document.lineCount; + + // once done, need to call the interactive editor, when working with the implentation interface case + if (action.fixName === 'fixClassIncorrectlyImplementsInterface') { + console.log('case when class incorrectly implements interface'); + await vscode.commands.executeCommand('interactiveEditor.start', { initialRange: { startLineNumber: 0, endLineNumber: numberLines, startColumn: 0, endColumn: 0 }, message: 'Implement the class from the interface', autoSend: true }); + } + // + + return codeActionResult; } } @@ -230,6 +251,9 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { + + console.log('inside of provideCodeActions'); + const file = this.client.toOpenTsFilePath(document); if (!file) { return; @@ -265,6 +289,9 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { + + console.log('inside of resolveCodeAction'); + if (!(codeAction instanceof VsCodeFixAllCodeAction) || !codeAction.tsAction.fixId) { return codeAction; } @@ -293,10 +320,14 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { + + console.log('inside of getFixesForDiagnostic'); + const args: Proto.CodeFixRequestArgs = { ...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range), errorCodes: [+(diagnostic.code!)] }; + // TODO: point of interest const response = await this.client.execute('getCodeFixes', args, token); if (response.type !== 'response' || !response.body) { return results; @@ -315,6 +346,9 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider | undefined, diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts index 4946a918df2..df5da0c7728 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts @@ -56,6 +56,8 @@ export class StartSessionAction extends EditorAction2 { } override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { + console.log('inside of the case when running the interactive editor start command'); + let options: InteractiveEditorRunOptions | undefined; const arg = _args[0]; if (arg && this._isInteractivEditorOptions(arg)) { From d812b91cbdc0947618d1ffe11f52dcbab75b6495 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Tue, 23 May 2023 17:18:12 +0200 Subject: [PATCH 03/65] Revert "keybinding widgets: press escape to clear, press again to hide" This reverts commit 616909cf7a87ecc6c3371aa11cbf815f9e4855d0. --- .../preferences/browser/keybindingWidgets.ts | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index d14d2de406c..020ecc21eb7 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -103,10 +103,6 @@ export class KeybindingsSearchWidget extends SearchWidget { return; } if (keyboardEvent.equals(KeyCode.Escape)) { - if (this._chords !== null) { - this.clear(); - } - this._onEscape.fire(); return; } @@ -185,8 +181,8 @@ export class DefineKeybindingWidget extends Widget { this._keybindingInputWidget.startRecordingKeys(); this._register(this._keybindingInputWidget.onKeybinding(keybinding => this.onKeybinding(keybinding))); this._register(this._keybindingInputWidget.onEnter(() => this.hide())); - this._register(this._keybindingInputWidget.onEscape(() => this.onEscape())); - this._register(this._keybindingInputWidget.onBlur(() => this.onBlur())); + this._register(this._keybindingInputWidget.onEscape(() => this.onCancel())); + this._register(this._keybindingInputWidget.onBlur(() => this.onCancel())); this._outputNode = dom.append(this._domNode.domNode, dom.$('.output')); this._showExistingKeybindingsNode = dom.append(this._domNode.domNode, dom.$('.existing')); @@ -280,21 +276,11 @@ export class DefineKeybindingWidget extends Widget { return label; } - private onBlur(): void { + private onCancel(): void { this._chords = null; this.hide(); } - private onEscape(): void { - if (this._chords !== null) { - this._chords = null; - dom.clearNode(this._outputNode); - dom.clearNode(this._showExistingKeybindingsNode); - return; - } - this.hide(); - } - private hide(): void { this._domNode.setDisplay('none'); this._isVisible = false; From e5a37e8d5fc0cba4f47eb2c4437e0cc65669a128 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 23 May 2023 17:26:33 +0200 Subject: [PATCH 04/65] setting the right end line number --- .../src/languageFeatures/documentSymbol.ts | 4 +- .../src/languageFeatures/quickFix.ts | 55 +++++++++++++------ 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/documentSymbol.ts b/extensions/typescript-language-features/src/languageFeatures/documentSymbol.ts index ef2d47ef942..349c04c3048 100644 --- a/extensions/typescript-language-features/src/languageFeatures/documentSymbol.ts +++ b/extensions/typescript-language-features/src/languageFeatures/documentSymbol.ts @@ -33,7 +33,7 @@ const getSymbolKind = (kind: string): vscode.SymbolKind => { return vscode.SymbolKind.Variable; }; -class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider { +export class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider { public constructor( private readonly client: ITypeScriptServiceClient, @@ -60,7 +60,7 @@ class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider return result; } - private static convertNavTree( + public static convertNavTree( resource: vscode.Uri, output: vscode.DocumentSymbol[], item: Proto.NavigationTree, diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index b83af8d2526..f838fb4e4eb 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -35,6 +35,21 @@ class ApplyCodeActionCommand implements Command { private readonly telemetryReporter: TelemetryReporter, ) { } + private findEndLine(startLine: number, navigationTree: Proto.NavigationTree[]): number { + + for (const node of navigationTree) { + const nodeStartLine = node.spans[0].start.line; + const nodeEndLine = node.spans[0].end.line; + + if (nodeStartLine === startLine) { + return nodeEndLine; + } else if (startLine > nodeStartLine && startLine <= nodeEndLine && node.childItems) { + return this.findEndLine(startLine, node.childItems); + } + } + return -1; + } + public async execute({ resource, action, diagnostic }: ApplyCodeActionCommand_args): Promise { /* __GDPR__ "quickFix.execute" : { @@ -45,33 +60,39 @@ class ApplyCodeActionCommand implements Command { ] } */ - console.log('resource : ', resource); - console.log('action : ', action); - console.log('diagnostic : ', diagnostic); this.telemetryReporter.logTelemetry('quickFix.execute', { fixName: action.fixName }); this.diagnosticManager.deleteDiagnostic(resource, diagnostic); - // TODO: point of interest const codeActionResult = await applyCodeActionCommands(this.client, action.commands, nulToken); - // TODO: how to find the end line number of the class? - // const startLine = diagnostic.range.start.line; - // Find end line using the outline - // vscode.window.activeTextEditor?.document - - const numberLines = vscode.window.activeTextEditor?.document.lineCount; - - // once done, need to call the interactive editor, when working with the implentation interface case if (action.fixName === 'fixClassIncorrectlyImplementsInterface') { - console.log('case when class incorrectly implements interface'); - await vscode.commands.executeCommand('interactiveEditor.start', { initialRange: { startLineNumber: 0, endLineNumber: numberLines, startColumn: 0, endColumn: 0 }, message: 'Implement the class from the interface', autoSend: true }); - } - // - return codeActionResult; + const diagnosticStartLine = diagnostic.range.start.line + 1; + const document = vscode.window.activeTextEditor?.document; + + if (document) { + const filepath = this.client.toOpenTsFilePath(document); + + if (filepath) { + const cancellationTokenSource = new vscode.CancellationTokenSource(); + const response = await this.client.execute('navtree', { file: filepath }, cancellationTokenSource.token); + + if (response.type === 'response' && response.body?.childItems) { + + const endLineFound = this.findEndLine(diagnosticStartLine, response.body.childItems); + const endLine = endLineFound !== -1 ? endLineFound : vscode.window.activeTextEditor?.document.lineCount; + const startLine = endLineFound !== -1 ? diagnosticStartLine : 0; + + await vscode.commands.executeCommand('interactiveEditor.start', { initialRange: { startLineNumber: startLine, endLineNumber: endLine, startColumn: 0, endColumn: 0 }, message: 'Implement the class using the interface', autoSend: true }); + } + } + } + return codeActionResult; + } + return false; } } From b2361a5c83eccaf172fe031dae27ff1a3a574062 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 23 May 2023 17:28:46 +0200 Subject: [PATCH 05/65] removing some console logs --- .../src/languageFeatures/quickFix.ts | 24 ------------------- .../src/languageFeatures/util/codeAction.ts | 1 - 2 files changed, 25 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index f838fb4e4eb..02c06c6f49f 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -36,11 +36,9 @@ class ApplyCodeActionCommand implements Command { ) { } private findEndLine(startLine: number, navigationTree: Proto.NavigationTree[]): number { - for (const node of navigationTree) { const nodeStartLine = node.spans[0].start.line; const nodeEndLine = node.spans[0].end.line; - if (nodeStartLine === startLine) { return nodeEndLine; } else if (startLine > nodeStartLine && startLine <= nodeEndLine && node.childItems) { @@ -60,7 +58,6 @@ class ApplyCodeActionCommand implements Command { ] } */ - this.telemetryReporter.logTelemetry('quickFix.execute', { fixName: action.fixName }); @@ -258,8 +255,6 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { - - console.log('inside of provideCodeActions'); - const file = this.client.toOpenTsFilePath(document); if (!file) { return; @@ -310,9 +302,6 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { - - console.log('inside of resolveCodeAction'); - if (!(codeAction instanceof VsCodeFixAllCodeAction) || !codeAction.tsAction.fixId) { return codeAction; } @@ -341,14 +330,10 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { - - console.log('inside of getFixesForDiagnostic'); - const args: Proto.CodeFixRequestArgs = { ...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range), errorCodes: [+(diagnostic.code!)] }; - // TODO: point of interest const response = await this.client.execute('getCodeFixes', args, token); if (response.type !== 'response' || !response.body) { return results; @@ -367,9 +352,6 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider | undefined, From 1ef21ddc304cb2d3e077d43627b7e0286125415b Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 23 May 2023 17:28:51 +0200 Subject: [PATCH 06/65] removing some console logs --- .../interactiveEditor/browser/interactiveEditorActions.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts index df5da0c7728..4946a918df2 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts @@ -56,8 +56,6 @@ export class StartSessionAction extends EditorAction2 { } override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor, ..._args: any[]) { - console.log('inside of the case when running the interactive editor start command'); - let options: InteractiveEditorRunOptions | undefined; const arg = _args[0]; if (arg && this._isInteractivEditorOptions(arg)) { From b278ac8a5484165289dcf3a6a350479c92e05f08 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 23 May 2023 17:29:40 +0200 Subject: [PATCH 07/65] resetting the privacy modifiers --- .../src/languageFeatures/documentSymbol.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/documentSymbol.ts b/extensions/typescript-language-features/src/languageFeatures/documentSymbol.ts index 349c04c3048..ef2d47ef942 100644 --- a/extensions/typescript-language-features/src/languageFeatures/documentSymbol.ts +++ b/extensions/typescript-language-features/src/languageFeatures/documentSymbol.ts @@ -33,7 +33,7 @@ const getSymbolKind = (kind: string): vscode.SymbolKind => { return vscode.SymbolKind.Variable; }; -export class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider { +class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolProvider { public constructor( private readonly client: ITypeScriptServiceClient, @@ -60,7 +60,7 @@ export class TypeScriptDocumentSymbolProvider implements vscode.DocumentSymbolPr return result; } - public static convertNavTree( + private static convertNavTree( resource: vscode.Uri, output: vscode.DocumentSymbol[], item: Proto.NavigationTree, From 1a50d63321463466998c30a4c7e309de8107d2fc Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 23 May 2023 18:18:44 +0200 Subject: [PATCH 08/65] added the arrow function into the private function --- .../src/languageFeatures/quickFix.ts | 99 +++++++++++-------- 1 file changed, 59 insertions(+), 40 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 02c06c6f49f..83f13a291a6 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -23,6 +23,7 @@ type ApplyCodeActionCommand_args = { readonly resource: vscode.Uri; readonly diagnostic: vscode.Diagnostic; readonly action: Proto.CodeFixAction; + readonly followupAction: (resource: vscode.Uri, diagnostic: vscode.Diagnostic, action: Proto.CodeFixAction, client: ITypeScriptServiceClient) => Promise | undefined; }; class ApplyCodeActionCommand implements Command { @@ -35,20 +36,7 @@ class ApplyCodeActionCommand implements Command { private readonly telemetryReporter: TelemetryReporter, ) { } - private findEndLine(startLine: number, navigationTree: Proto.NavigationTree[]): number { - for (const node of navigationTree) { - const nodeStartLine = node.spans[0].start.line; - const nodeEndLine = node.spans[0].end.line; - if (nodeStartLine === startLine) { - return nodeEndLine; - } else if (startLine > nodeStartLine && startLine <= nodeEndLine && node.childItems) { - return this.findEndLine(startLine, node.childItems); - } - } - return -1; - } - - public async execute({ resource, action, diagnostic }: ApplyCodeActionCommand_args): Promise { + public async execute({ resource, action, diagnostic, followupAction }: ApplyCodeActionCommand_args): Promise { /* __GDPR__ "quickFix.execute" : { "owner": "mjbvz", @@ -58,38 +46,21 @@ class ApplyCodeActionCommand implements Command { ] } */ + + console.log('resource : ', resource); + console.log('action : ', action); + console.log('diagnostic : ', diagnostic); + this.telemetryReporter.logTelemetry('quickFix.execute', { fixName: action.fixName }); this.diagnosticManager.deleteDiagnostic(resource, diagnostic); const codeActionResult = await applyCodeActionCommands(this.client, action.commands, nulToken); - - if (action.fixName === 'fixClassIncorrectlyImplementsInterface') { - - const diagnosticStartLine = diagnostic.range.start.line + 1; - const document = vscode.window.activeTextEditor?.document; - - if (document) { - const filepath = this.client.toOpenTsFilePath(document); - - if (filepath) { - const cancellationTokenSource = new vscode.CancellationTokenSource(); - const response = await this.client.execute('navtree', { file: filepath }, cancellationTokenSource.token); - - if (response.type === 'response' && response.body?.childItems) { - - const endLineFound = this.findEndLine(diagnosticStartLine, response.body.childItems); - const endLine = endLineFound !== -1 ? endLineFound : vscode.window.activeTextEditor?.document.lineCount; - const startLine = endLineFound !== -1 ? diagnosticStartLine : 0; - - await vscode.commands.executeCommand('interactiveEditor.start', { initialRange: { startLineNumber: startLine, endLineNumber: endLine, startColumn: 0, endColumn: 0 }, message: 'Implement the class using the interface', autoSend: true }); - } - } - } - return codeActionResult; + if (followupAction) { + await followupAction(resource, diagnostic, action, this.client); } - return false; + return codeActionResult; } } @@ -357,17 +328,65 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { + + const findScopeEndLine = (startLine: number, navigationTree: Proto.NavigationTree[]): number => { + for (const node of navigationTree) { + const nodeStartLine = node.spans[0].start.line; + const nodeEndLine = node.spans[0].end.line; + if (nodeStartLine === startLine) { + return nodeEndLine; + } else if (startLine > nodeStartLine && startLine <= nodeEndLine && node.childItems) { + return findScopeEndLine(startLine, node.childItems); + } + } + return -1; + }; + + console.log('inside of classIncorrectlyImplementsInterfaceFollowupAction'); + + if (action.fixName === 'fixClassIncorrectlyImplementsInterface') { + + const diagnosticStartLine = diagnostic.range.start.line + 1; + const document = vscode.window.activeTextEditor?.document; + + if (document) { + const filepath = client.toOpenTsFilePath(document); + + if (filepath) { + const cancellationTokenSource = new vscode.CancellationTokenSource(); + const response = await client.execute('navtree', { file: filepath }, cancellationTokenSource.token); + + if (response.type === 'response' && response.body?.childItems) { + + const endLineFound = findScopeEndLine(diagnosticStartLine, response.body.childItems); + const endLine = endLineFound !== -1 ? endLineFound : vscode.window.activeTextEditor?.document.lineCount; + const startLine = endLineFound !== -1 ? diagnosticStartLine : 0; + + await vscode.commands.executeCommand('interactiveEditor.start', { initialRange: { startLineNumber: startLine, endLineNumber: endLine, startColumn: 0, endColumn: 0 }, message: 'Implement the class using the interface', autoSend: true }); + } + } + } + } + } + private getSingleFixForTsCodeAction( resource: vscode.Uri, diagnostic: vscode.Diagnostic, tsAction: Proto.CodeFixAction ): VsCodeCodeAction { + let followupAction; + const fixName = tsAction.fixName; + switch (fixName) { + case 'fixClassIncorrectlyImplementsInterface': + followupAction = this.classIncorrectlyImplementsInterfaceFollowupAction; + } const codeAction = new VsCodeCodeAction(tsAction, tsAction.description, vscode.CodeActionKind.QuickFix); codeAction.edit = getEditForCodeAction(this.client, tsAction); codeAction.diagnostics = [diagnostic]; codeAction.command = { command: ApplyCodeActionCommand.ID, - arguments: [{ action: tsAction, diagnostic, resource }], + arguments: [{ action: tsAction, diagnostic, resource, followupAction }], title: '' }; return codeAction; From b07e68e5fb97d1f785b321c1b35dc3cda12dce1e Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 23 May 2023 18:35:36 +0200 Subject: [PATCH 09/65] cleaning the code --- .../src/languageFeatures/quickFix.ts | 63 +++++++------------ 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 83f13a291a6..076ad1a5da4 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -20,10 +20,10 @@ import { applyCodeActionCommands, getEditForCodeAction } from './util/codeAction import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration'; type ApplyCodeActionCommand_args = { - readonly resource: vscode.Uri; + readonly document: vscode.TextDocument; readonly diagnostic: vscode.Diagnostic; readonly action: Proto.CodeFixAction; - readonly followupAction: (resource: vscode.Uri, diagnostic: vscode.Diagnostic, action: Proto.CodeFixAction, client: ITypeScriptServiceClient) => Promise | undefined; + readonly followupAction: (document: vscode.TextDocument, diagnostic: vscode.Diagnostic, action: Proto.CodeFixAction, client: ITypeScriptServiceClient) => Promise | undefined; }; class ApplyCodeActionCommand implements Command { @@ -36,7 +36,7 @@ class ApplyCodeActionCommand implements Command { private readonly telemetryReporter: TelemetryReporter, ) { } - public async execute({ resource, action, diagnostic, followupAction }: ApplyCodeActionCommand_args): Promise { + public async execute({ document, action, diagnostic, followupAction }: ApplyCodeActionCommand_args): Promise { /* __GDPR__ "quickFix.execute" : { "owner": "mjbvz", @@ -46,19 +46,14 @@ class ApplyCodeActionCommand implements Command { ] } */ - - console.log('resource : ', resource); - console.log('action : ', action); - console.log('diagnostic : ', diagnostic); - this.telemetryReporter.logTelemetry('quickFix.execute', { fixName: action.fixName }); - this.diagnosticManager.deleteDiagnostic(resource, diagnostic); + this.diagnosticManager.deleteDiagnostic(document.uri, diagnostic); const codeActionResult = await applyCodeActionCommands(this.client, action.commands, nulToken); if (followupAction) { - await followupAction(resource, diagnostic, action, this.client); + await followupAction(document, diagnostic, action, this.client); } return codeActionResult; } @@ -323,12 +318,12 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { + private async classIncorrectlyImplementsInterfaceFollowupAction(document: vscode.TextDocument, diagnostic: vscode.Diagnostic, _action: Proto.CodeFixAction, client: ITypeScriptServiceClient): Promise { const findScopeEndLine = (startLine: number, navigationTree: Proto.NavigationTree[]): number => { for (const node of navigationTree) { @@ -342,42 +337,28 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider{ action: tsAction, diagnostic, resource, followupAction }], + arguments: [{ action: tsAction, diagnostic, document, followupAction }], title: '' }; return codeAction; From 0f24c24e2e90ea0dfa36282870001bc846e09621 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Tue, 23 May 2023 18:58:38 +0000 Subject: [PATCH 10/65] Add `checkProposedApiEnabled` check when using env collection workspace proposal --- src/vs/workbench/api/common/extHostTerminalService.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index d20f3a89f67..12e7228ef52 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -25,7 +25,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { Promises } from 'vs/base/common/async'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { ViewColumn } from 'vs/workbench/api/common/extHostTypeConverters'; -import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable { @@ -898,6 +898,10 @@ class UnifiedEnvironmentVariableCollection { } getScopedEnvironmentVariableCollection(scope: vscode.EnvironmentVariableScope | undefined): IEnvironmentVariableCollection { + if (this._extension) { + // TODO: This should be removed when the env var extension API(s) are stabilized + checkProposedApiEnabled(this._extension, 'envCollectionWorkspace'); + } const scopedCollectionKey = this.getScopeKey(scope); let scopedCollection = this.scopedCollections.get(scopedCollectionKey); if (!scopedCollection) { From 726e9bb1655bfd7e20443786aec334da8c816259 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Tue, 23 May 2023 19:02:41 +0000 Subject: [PATCH 11/65] Fix other places --- src/vs/workbench/api/common/extHostTerminalService.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 12e7228ef52..d30973ec764 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -25,7 +25,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { Promises } from 'vs/base/common/async'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { ViewColumn } from 'vs/workbench/api/common/extHostTypeConverters'; -import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable { @@ -914,21 +914,21 @@ class UnifiedEnvironmentVariableCollection { replace(variable: string, value: string, options: vscode.EnvironmentVariableMutatorOptions | undefined, scope: vscode.EnvironmentVariableScope | undefined): void { if (this._extension && options) { - isProposedApiEnabled(this._extension, 'envCollectionOptions'); + checkProposedApiEnabled(this._extension, 'envCollectionOptions'); } this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Replace, options: options ?? {}, scope }); } append(variable: string, value: string, options: vscode.EnvironmentVariableMutatorOptions | undefined, scope: vscode.EnvironmentVariableScope | undefined): void { if (this._extension && options) { - isProposedApiEnabled(this._extension, 'envCollectionOptions'); + checkProposedApiEnabled(this._extension, 'envCollectionOptions'); } this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Append, options: options ?? {}, scope }); } prepend(variable: string, value: string, options: vscode.EnvironmentVariableMutatorOptions | undefined, scope: vscode.EnvironmentVariableScope | undefined): void { if (this._extension && options) { - isProposedApiEnabled(this._extension, 'envCollectionOptions'); + checkProposedApiEnabled(this._extension, 'envCollectionOptions'); } this._setIfDiffers(variable, { value, type: EnvironmentVariableMutatorType.Prepend, options: options ?? {}, scope }); } From b4a853bac9e66479435a3525f0c7b2f43c7ece2a Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Tue, 23 May 2023 19:40:55 +0000 Subject: [PATCH 12/65] Update package.json for vscode-api-tests --- extensions/vscode-api-tests/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 63a3e7bf194..a802f11c315 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -46,7 +46,9 @@ "workspaceTrust", "telemetry", "windowActivity", - "interactiveUserActions" + "interactiveUserActions", + "envCollectionWorkspace", + "envCollectionOptions" ], "private": true, "activationEvents": [], From ab2e73aa03fd5a5867d704fba9387502d714486a Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Tue, 23 May 2023 19:59:32 +0000 Subject: [PATCH 13/65] Fix test? --- src/vs/workbench/api/common/extHostTerminalService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index d30973ec764..40b5a41ee4b 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -898,7 +898,7 @@ class UnifiedEnvironmentVariableCollection { } getScopedEnvironmentVariableCollection(scope: vscode.EnvironmentVariableScope | undefined): IEnvironmentVariableCollection { - if (this._extension) { + if (this._extension && scope) { // TODO: This should be removed when the env var extension API(s) are stabilized checkProposedApiEnabled(this._extension, 'envCollectionWorkspace'); } From cd7687c9d1de6ccbe51effe580f14e4d7eab1e61 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Tue, 23 May 2023 15:11:19 -0500 Subject: [PATCH 14/65] use code block index and count in aria label --- .../contrib/chat/browser/chatListRenderer.ts | 19 ++++++++++++++++--- .../contrib/chat/common/chatViewModel.ts | 4 ++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index f8decdfdd46..0c705b82545 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -425,6 +425,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.codeBlocksByEditorUri.delete(ref.object.textModel.uri))); @@ -435,6 +436,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.codeBlocksByResponseId.delete(element.id))); } @@ -534,7 +536,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider { this.editor.updateOptions(this.getEditorOptionsFromConfig()); })); @@ -723,7 +736,7 @@ class CodeBlockPart extends Disposable implements IChatResultCodeBlockPart { this.setLanguage(vscodeLanguageId); this.layout(width); - + this.editor.updateOptions({ ariaLabel: localize('chat.codeBlockLabel', "Code block {0}", data.codeBlockIndex + 1) }); this.toolbar.context = { code: data.text, codeBlockIndex: data.codeBlockIndex, diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index ba66ad2c6cd..9a1a861daec 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -79,6 +79,8 @@ export interface IChatResponseViewModel { readonly commandFollowups?: IChatResponseCommandFollowup[]; readonly errorDetails?: IChatResponseErrorDetails; readonly contentUpdateTimings?: IChatLiveUpdateData; + codeBlockCount?: number; + codeBlockIndex?: number; renderData?: IChatResponseRenderData; currentRenderedHeight: number | undefined; setVote(vote: InteractiveSessionVoteDirection): void; @@ -258,6 +260,8 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi renderData: IChatResponseRenderData | undefined = undefined; + codeBlockCount: number | undefined; + currentRenderedHeight: number | undefined; private _contentUpdateTimings: IChatLiveUpdateData | undefined = undefined; From 6ae512e8b8cea672235387b2b2a26c6156e9373c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Tue, 23 May 2023 15:25:05 -0500 Subject: [PATCH 15/65] Update src/vs/workbench/contrib/chat/browser/chatListRenderer.ts --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 0c705b82545..d7d2b996efa 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -554,7 +554,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider Date: Tue, 23 May 2023 14:21:16 -0700 Subject: [PATCH 16/65] replace the content of the scrollable div, rather than the div itself --- extensions/notebook-renderers/src/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index 10fd6c99eb2..1f9979af4bb 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -265,6 +265,11 @@ function scrollingEnabled(output: OutputItem, options: RenderOptions) { metadata.scrollable : options.outputScrolling; } +// div.cell_container +// div.output_container +// div.output.output-stream <-- outputElement parameter +// div.scrollable? tabindex="0" <-- contentParent +// div output-item-id="{guid}" <-- content from outputItem parameter function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error: boolean, ctx: IRichRenderContext): IDisposable { const disposableStore = createDisposableStore(); const outputScrolling = scrollingEnabled(outputInfo, ctx.settings); @@ -281,7 +286,7 @@ function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error: const scrollTop = outputScrolling ? findScrolledHeight(outputElement) : undefined; // If the previous output item for the same cell was also a stream, append this output to the previous - const existingContentParent = getPreviousMatchingContentGroup(outputElement); + const existingContentParent = getPreviousMatchingContentGroup(outputElement) || outputElement.querySelector('div'); if (existingContentParent) { const existing = existingContentParent.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null; if (existing) { From 43d5cc452ca5ef3cd9d3128f37810ff4f11e995c Mon Sep 17 00:00:00 2001 From: aamunger Date: Tue, 23 May 2023 16:20:54 -0700 Subject: [PATCH 17/65] account for running a cell with one output after that cell previously produced multiple streaming outputs --- extensions/notebook-renderers/src/index.ts | 3 +++ .../src/test/notebookRenderer.test.ts | 27 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index 1f9979af4bb..eba19067dbf 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -291,6 +291,9 @@ function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error: const existing = existingContentParent.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null; if (existing) { existing.replaceWith(content); + while (content.nextSibling) { + content.nextSibling.remove(); + } } else { existingContentParent.appendChild(content); } diff --git a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts index 3ce2c4019e8..f01aa630956 100644 --- a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts +++ b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts @@ -146,7 +146,7 @@ suite('Notebook builtin output renderer', () => { const inserted = outputElement.firstChild as HTMLElement; assert.ok(inserted, `nothing appended to output element: ${outputElement.innerHTML}`); - assert.ok(!outputElement.classList.contains('remove-padding'), `Padding should not be removed for non-scrollable outputs: ${outputElement.classList}`); + assert.ok(outputElement.classList.contains('remove-padding'), `Padding should be removed for non-scrollable outputs: ${outputElement.classList}`); assert.ok(!inserted.classList.contains('word-wrap') && !inserted.classList.contains('scrollable'), `output content classList should not contain word-wrap and scrollable ${inserted.classList}`); assert.ok(inserted.innerHTML.indexOf('>content -1, `Content was not added to output element: ${outputElement.innerHTML}`); @@ -225,5 +225,30 @@ suite('Notebook builtin output renderer', () => { assert.ok(inserted.innerHTML.indexOf('>second stream content -1, `Content was not added to output element: ${outputElement.innerHTML}`); assert.ok(inserted.innerHTML.indexOf('>third stream content -1, `Content was not added to output element: ${outputElement.innerHTML}`); }); + + test(`Multiple adjacent streaming outputs, rerendering the first should erase the rest`, async () => { + const context = createContext(); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputHtml = new OutputHtml(); + const outputElement = outputHtml.getFirstOuputElement(); + const outputItem1 = createOutputItem('first stream content', stdoutMimeType, '1'); + const outputItem2 = createOutputItem('second stream content', stdoutMimeType, '2'); + const outputItem3 = createOutputItem('third stream content', stderrMimeType, '3'); + await renderer!.renderOutputItem(outputItem1, outputElement); + await renderer!.renderOutputItem(outputItem2, outputHtml.appendOutputElement()); + await renderer!.renderOutputItem(outputItem3, outputHtml.appendOutputElement()); + const newOutputItem1 = createOutputItem('replaced content', stderrMimeType, '1'); + await renderer!.renderOutputItem(newOutputItem1, outputElement); + + + const inserted = outputElement.firstChild as HTMLElement; + assert.ok(inserted, `nothing appended to output element: ${outputElement.innerHTML}`); + assert.ok(inserted.innerHTML.indexOf('>replaced content -1, `Content was not added to output element: ${outputElement.innerHTML}`); + assert.ok(inserted.innerHTML.indexOf('>first stream contentsecond stream contentthird stream content Date: Tue, 23 May 2023 16:25:34 -0700 Subject: [PATCH 18/65] comment --- extensions/notebook-renderers/src/index.ts | 1 + scripts/test-integration.bat | 73 ++-------------------- 2 files changed, 5 insertions(+), 69 deletions(-) diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index eba19067dbf..0351509f922 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -292,6 +292,7 @@ function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error: if (existing) { existing.replaceWith(content); while (content.nextSibling) { + // clear out any stale content if we had previously combined streaming outputs into this one content.nextSibling.remove(); } } else { diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index e5b356f3dec..0c331a7bca1 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -24,82 +24,17 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( echo Storing crash reports into '%VSCODECRASHDIR%'. echo Storing log files into '%VSCODELOGSDIR%'. - -:: Tests standalone (AMD) - -echo. -echo ### node.js integration tests -call .\scripts\test.bat --runGlob **\*.integrationTest.js %* -if %errorlevel% neq 0 exit /b %errorlevel% - - :: Tests in the extension host set API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=%VSCODECRASHDIR% --logsPath=%VSCODELOGSDIR% --no-cached-data --disable-updates --disable-keytar --disable-extensions --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% echo. -echo ### API tests (folder) -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests %API_TESTS_EXTRA_ARGS% +echo ### Notebook Output tests +set NBOUTWORKSPACE=%TEMPDIR%\nbout-%RANDOM% +mkdir %NBOUTWORKSPACE% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %NBOUTWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\notebook-renderers --extensionTestsPath=%~dp0\..\extensions\notebook-renderers\out\test %API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% -echo. -echo ### API tests (workspace) -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests %API_TESTS_EXTRA_ARGS% -if %errorlevel% neq 0 exit /b %errorlevel% - -echo. -echo ### Colorize tests -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out %API_TESTS_EXTRA_ARGS% -if %errorlevel% neq 0 exit /b %errorlevel% - -echo. -echo ### TypeScript tests -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\typescript-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\typescript-language-features --extensionTestsPath=%~dp0\..\extensions\typescript-language-features\out\test\unit %API_TESTS_EXTRA_ARGS% -if %errorlevel% neq 0 exit /b %errorlevel% - -echo. -echo ### Markdown tests -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\markdown-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\markdown-language-features --extensionTestsPath=%~dp0\..\extensions\markdown-language-features\out\test %API_TESTS_EXTRA_ARGS% -if %errorlevel% neq 0 exit /b %errorlevel% - -echo. -echo ### Emmet tests -call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\emmet\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test %API_TESTS_EXTRA_ARGS% -if %errorlevel% neq 0 exit /b %errorlevel% - -echo. -echo ### Git tests -for /f "delims=" %%i in ('node -p "require('fs').realpathSync.native(require('os').tmpdir())"') do set TEMPDIR=%%i -set GITWORKSPACE=%TEMPDIR%\git-%RANDOM% -mkdir %GITWORKSPACE% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test %API_TESTS_EXTRA_ARGS% -if %errorlevel% neq 0 exit /b %errorlevel% - -echo. -echo ### Ipynb tests -set IPYNBWORKSPACE=%TEMPDIR%\ipynb-%RANDOM% -mkdir %IPYNBWORKSPACE% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %IPYNBWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\ipynb --extensionTestsPath=%~dp0\..\extensions\ipynb\out\test %API_TESTS_EXTRA_ARGS% -if %errorlevel% neq 0 exit /b %errorlevel% - -echo. -echo ### Configuration editing tests -set CFWORKSPACE=%TEMPDIR%\cf-%RANDOM% -mkdir %CFWORKSPACE% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %CFWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\configuration-editing --extensionTestsPath=%~dp0\..\extensions\configuration-editing\out\test %API_TESTS_EXTRA_ARGS% -if %errorlevel% neq 0 exit /b %errorlevel% - -:: Tests standalone (CommonJS) - -echo. -echo ### CSS tests -call %~dp0\node-electron.bat %~dp0\..\extensions\css-language-features/server/test/index.js -if %errorlevel% neq 0 exit /b %errorlevel% - -echo. -echo ### HTML tests -call %~dp0\node-electron.bat %~dp0\..\extensions\html-language-features/server/test/index.js -if %errorlevel% neq 0 exit /b %errorlevel% :: Cleanup From 6243becc260f708e73f5e17c87a397a5691e5d56 Mon Sep 17 00:00:00 2001 From: aamunger Date: Tue, 23 May 2023 16:31:18 -0700 Subject: [PATCH 19/65] revert test script --- scripts/test-integration.bat | 73 ++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 0c331a7bca1..e5b356f3dec 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -24,17 +24,82 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( echo Storing crash reports into '%VSCODECRASHDIR%'. echo Storing log files into '%VSCODELOGSDIR%'. + +:: Tests standalone (AMD) + +echo. +echo ### node.js integration tests +call .\scripts\test.bat --runGlob **\*.integrationTest.js %* +if %errorlevel% neq 0 exit /b %errorlevel% + + :: Tests in the extension host set API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=%VSCODECRASHDIR% --logsPath=%VSCODELOGSDIR% --no-cached-data --disable-updates --disable-keytar --disable-extensions --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% echo. -echo ### Notebook Output tests -set NBOUTWORKSPACE=%TEMPDIR%\nbout-%RANDOM% -mkdir %NBOUTWORKSPACE% -call "%INTEGRATION_TEST_ELECTRON_PATH%" %NBOUTWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\notebook-renderers --extensionTestsPath=%~dp0\..\extensions\notebook-renderers\out\test %API_TESTS_EXTRA_ARGS% +echo ### API tests (folder) +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests %API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% +echo. +echo ### API tests (workspace) +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Colorize tests +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### TypeScript tests +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\typescript-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\typescript-language-features --extensionTestsPath=%~dp0\..\extensions\typescript-language-features\out\test\unit %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Markdown tests +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\markdown-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\markdown-language-features --extensionTestsPath=%~dp0\..\extensions\markdown-language-features\out\test %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Emmet tests +call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\emmet\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Git tests +for /f "delims=" %%i in ('node -p "require('fs').realpathSync.native(require('os').tmpdir())"') do set TEMPDIR=%%i +set GITWORKSPACE=%TEMPDIR%\git-%RANDOM% +mkdir %GITWORKSPACE% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %GITWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\git --extensionTestsPath=%~dp0\..\extensions\git\out\test %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Ipynb tests +set IPYNBWORKSPACE=%TEMPDIR%\ipynb-%RANDOM% +mkdir %IPYNBWORKSPACE% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %IPYNBWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\ipynb --extensionTestsPath=%~dp0\..\extensions\ipynb\out\test %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Configuration editing tests +set CFWORKSPACE=%TEMPDIR%\cf-%RANDOM% +mkdir %CFWORKSPACE% +call "%INTEGRATION_TEST_ELECTRON_PATH%" %CFWORKSPACE% --extensionDevelopmentPath=%~dp0\..\extensions\configuration-editing --extensionTestsPath=%~dp0\..\extensions\configuration-editing\out\test %API_TESTS_EXTRA_ARGS% +if %errorlevel% neq 0 exit /b %errorlevel% + +:: Tests standalone (CommonJS) + +echo. +echo ### CSS tests +call %~dp0\node-electron.bat %~dp0\..\extensions\css-language-features/server/test/index.js +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### HTML tests +call %~dp0\node-electron.bat %~dp0\..\extensions\html-language-features/server/test/index.js +if %errorlevel% neq 0 exit /b %errorlevel% :: Cleanup From 2eb365c80d57491f40b700a8a09771bf707c27d3 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 23 May 2023 16:45:48 -0700 Subject: [PATCH 20/65] Add commands to move chat sessions between the editor and sidebar (#183270) --- .../chat/browser/actions/chatActions.ts | 94 ---------- .../contrib/chat/browser/actions/chatClear.ts | 21 +-- .../chat/browser/actions/chatClearActions.ts | 109 ++++++++++++ .../chat/browser/actions/chatMoveActions.ts | 162 ++++++++++++++++++ .../contrib/chat/browser/chat.contribution.ts | 2 + .../browser/chatContributionServiceImpl.ts | 19 +- .../contrib/chat/browser/chatEditorInput.ts | 1 + .../contrib/chat/browser/chatViewPane.ts | 11 +- 8 files changed, 307 insertions(+), 112 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts create mode 100644 src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index d65c1ec6980..05cb749ccad 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -14,9 +14,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; -import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; import { runAccessibilityHelpAction } from 'vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp'; -import { clearChatEditor, clearChatSession } from 'vs/workbench/contrib/chat/browser/actions/chatClear'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; @@ -53,36 +51,6 @@ export function registerChatActions() { } }); - registerAction2(class ClearEditorAction extends Action2 { - constructor() { - super({ - id: 'workbench.action.chatEditor.clear', - title: { - value: localize('interactiveSession.clear.label', "Clear"), - original: 'Clear' - }, - icon: Codicon.clearAll, - f1: false, - menu: [{ - id: MenuId.EditorTitle, - group: 'navigation', - order: 0, - when: ActiveEditorContext.isEqualTo(ChatEditorInput.EditorID), - }] - }); - } - async run(accessor: ServicesAccessor, ...args: any[]) { - const widgetService = accessor.get(IChatWidgetService); - - const widget = widgetService.lastFocusedWidget; - if (!widget) { - return; - } - - await clearChatEditor(accessor, widget); - } - }); - registerAction2(class ClearChatHistoryAction extends Action2 { constructor() { super({ @@ -165,38 +133,6 @@ export function registerChatActions() { widgetService.lastFocusedWidget?.focusInput(); } }); - - registerAction2(class GlobalClearChatAction extends Action2 { - constructor() { - super({ - id: `workbench.action.chat.clear`, - title: { - value: localize('interactiveSession.clear.label', "Clear"), - original: 'Clear' - }, - category: CHAT_CATEGORY, - icon: Codicon.clearAll, - precondition: CONTEXT_PROVIDER_EXISTS, - f1: true, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.WinCtrl | KeyCode.KeyL, - when: CONTEXT_IN_CHAT_SESSION - } - }); - } - - async run(accessor: ServicesAccessor, ...args: any[]) { - const widgetService = accessor.get(IChatWidgetService); - - const widget = widgetService.lastFocusedWidget; - if (!widget) { - return; - } - - await clearChatSession(accessor, widget); - } - }); } export function getOpenChatEditorAction(id: string, label: string, when?: string) { @@ -218,36 +154,6 @@ export function getOpenChatEditorAction(id: string, label: string, when?: string }; } -const getClearChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly & { viewId: string } => ({ - viewId, - id: `workbench.action.chat.${providerId}.clear`, - title: { - value: localize('interactiveSession.clear.label', "Clear"), - original: 'Clear' - }, - menu: { - id: MenuId.ViewTitle, - when: ContextKeyExpr.equals('view', viewId), - group: 'navigation', - order: 0 - }, - category: CHAT_CATEGORY, - icon: Codicon.clearAll, - f1: false -}); - -export function getClearAction(viewId: string, providerId: string) { - return class ClearAction extends ViewAction { - constructor() { - super(getClearChatActionDescriptorForViewTitle(viewId, providerId)); - } - - async runInView(accessor: ServicesAccessor, view: ChatViewPane) { - await view.clear(); - } - }; -} - const getHistoryChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly & { viewId: string } => ({ viewId, id: `workbench.action.chat.${providerId}.history`, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts index cb6795fbab4..f87796a56ef 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClear.ts @@ -5,16 +5,15 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IViewsService } from 'vs/workbench/common/views'; -import { IChatWidgetService, type IChatViewPane, type IChatWidget } from 'vs/workbench/contrib/chat/browser/chat'; +import { type IChatViewPane, type IChatWidget } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export async function clearChatSession(accessor: ServicesAccessor, widget: IChatWidget): Promise { - const viewsService = accessor.get(IViewsService); - if ('viewId' in widget.viewContext) { + const viewsService = accessor.get(IViewsService); // This cast is to break a circular dependency- ideally this would not be called directly for `/clear` // from the widget class, but from some contribution. const view = viewsService.getViewWithId(widget.viewContext.viewId); @@ -24,17 +23,19 @@ export async function clearChatSession(accessor: ServicesAccessor, widget: IChat (view as any as IChatViewPane).clear(); } else { - await clearChatEditor(accessor, widget); + await clearChatEditor(accessor); } } -export async function clearChatEditor(accessor: ServicesAccessor, widget: IChatWidget): Promise { +export async function clearChatEditor(accessor: ServicesAccessor): Promise { const editorService = accessor.get(IEditorService); const editorGroupsService = accessor.get(IEditorGroupsService); - const widgetService = accessor.get(IChatWidgetService); - await editorService.replaceEditors([{ - editor: editorService.activeEditor!, - replacement: { resource: ChatEditorInput.getNewEditorUri(), options: { target: { providerId: widgetService.lastFocusedWidget!.providerId, pinned: true } } } - }], editorGroupsService.activeGroup); + const chatEditorInput = editorService.activeEditor; + if (chatEditorInput instanceof ChatEditorInput && chatEditorInput.providerId) { + await editorService.replaceEditors([{ + editor: chatEditorInput, + replacement: { resource: ChatEditorInput.getNewEditorUri(), options: { target: { providerId: chatEditorInput.providerId, pinned: true } } } + }], editorGroupsService.activeGroup); + } } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts new file mode 100644 index 00000000000..93ef5556d75 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Codicon } from 'vs/base/common/codicons'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { localize } from 'vs/nls'; +import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; +import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; +import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; +import { clearChatEditor, clearChatSession } from 'vs/workbench/contrib/chat/browser/actions/chatClear'; +import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; +import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; +import { CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; + +export function registerClearActions() { + + registerAction2(class ClearEditorAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chatEditor.clear', + title: { + value: localize('interactiveSession.clear.label', "Clear"), + original: 'Clear' + }, + icon: Codicon.clearAll, + f1: false, + menu: [{ + id: MenuId.EditorTitle, + group: 'navigation', + order: 0, + when: ActiveEditorContext.isEqualTo(ChatEditorInput.EditorID), + }] + }); + } + async run(accessor: ServicesAccessor, ...args: any[]) { + await clearChatEditor(accessor); + } + }); + + + registerAction2(class GlobalClearChatAction extends Action2 { + constructor() { + super({ + id: `workbench.action.chat.clear`, + title: { + value: localize('interactiveSession.clear.label', "Clear"), + original: 'Clear' + }, + category: CHAT_CATEGORY, + icon: Codicon.clearAll, + precondition: CONTEXT_PROVIDER_EXISTS, + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.WinCtrl | KeyCode.KeyL, + when: CONTEXT_IN_CHAT_SESSION + } + }); + } + + async run(accessor: ServicesAccessor, ...args: any[]) { + const widgetService = accessor.get(IChatWidgetService); + + const widget = widgetService.lastFocusedWidget; + if (!widget) { + return; + } + + await clearChatSession(accessor, widget); + } + }); +} + +const getClearChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly & { viewId: string } => ({ + viewId, + id: `workbench.action.chat.${providerId}.clear`, + title: { + value: localize('interactiveSession.clear.label', "Clear"), + original: 'Clear' + }, + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.equals('view', viewId), + group: 'navigation', + order: 0 + }, + category: CHAT_CATEGORY, + icon: Codicon.clearAll, + f1: false +}); + +export function getClearAction(viewId: string, providerId: string) { + return class ClearAction extends ViewAction { + constructor() { + super(getClearChatActionDescriptorForViewTitle(viewId, providerId)); + } + + async runInView(accessor: ServicesAccessor, view: ChatViewPane) { + await view.clear(); + } + }; +} diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts new file mode 100644 index 00000000000..9ea1e2fd1cc --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -0,0 +1,162 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Codicon } from 'vs/base/common/codicons'; +import { localize } from 'vs/nls'; +import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; +import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; +import { IViewsService } from 'vs/workbench/common/views'; +import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; +import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; +import { ChatEditorInput } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; +import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; +import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +const getMoveToEditorChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly & { viewId: string } => ({ + id: `workbench.action.chat.${providerId}.openInEditor`, + title: { + value: localize('chat.openInEditor.label', "Open In Editor"), + original: 'Open In Editor' + }, + category: CHAT_CATEGORY, + icon: Codicon.arrowLeft, + precondition: CONTEXT_PROVIDER_EXISTS, + f1: false, + viewId, + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', viewId), ContextKeyExpr.deserialize('config.chat.experimiental.moveIcons')), + group: 'navigation', + order: 0 + }, +}); + +export function getMoveToEditorAction(viewId: string, providerId: string) { + return class MoveToEditorAction extends ViewAction { + constructor() { + super(getMoveToEditorChatActionDescriptorForViewTitle(viewId, providerId)); + } + + async runInView(accessor: ServicesAccessor, view: ChatViewPane) { + const viewModel = view.widget.viewModel; + if (!viewModel) { + return; + } + + const editorService = accessor.get(IEditorService); + view.clear(); + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { sessionId: viewModel.sessionId }, pinned: true } }); + } + }; +} + +const getMoveToSidebarChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly & { viewId: string } => ({ + id: `workbench.action.chat.${providerId}.openInSidebar`, + title: { + value: localize('chat.openInSidebar.label', "Open In Sidebar"), + original: 'Open In Sidebar' + }, + category: CHAT_CATEGORY, + icon: Codicon.arrowRight, + precondition: CONTEXT_PROVIDER_EXISTS, + f1: false, // TODO + viewId, + menu: [{ + id: MenuId.EditorTitle, + group: 'navigation', + order: 0, + when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(ChatEditorInput.EditorID), ContextKeyExpr.deserialize('config.chat.experimental.moveIcons')), + }] +}); + +export function getMoveToSidebarAction(viewId: string, providerId: string) { + return class MoveToSidebarAction extends Action2 { + constructor() { + super(getMoveToSidebarChatActionDescriptorForViewTitle(viewId, providerId)); + } + + override async run(accessor: ServicesAccessor, ...args: any[]) { + return moveToSidebar(accessor); + } + }; +} + +async function moveToSidebar(accessor: ServicesAccessor): Promise { + const viewsService = accessor.get(IViewsService); + const editorService = accessor.get(IEditorService); + const chatContribService = accessor.get(IChatContributionService); + const editorGroupService = accessor.get(IEditorGroupsService); + + const chatEditorInput = editorService.activeEditor; + if (chatEditorInput instanceof ChatEditorInput && chatEditorInput.sessionId && chatEditorInput.providerId) { + await editorService.closeEditor({ editor: chatEditorInput, groupId: editorGroupService.activeGroup.id }); + const viewId = chatContribService.getViewIdForProvider(chatEditorInput.providerId); + const view = await viewsService.openView(viewId) as ChatViewPane; + view.loadSession(chatEditorInput.sessionId); + } +} + +export function registerMoveActions() { + registerAction2(class GlobalMoveToEditorAction extends Action2 { + constructor() { + super({ + id: `workbench.action.chat.openInEditor`, + title: { + value: localize('interactiveSession.openInEditor.label', "Open Session In Editor"), + original: 'Open Session In Editor' + }, + category: CHAT_CATEGORY, + precondition: CONTEXT_PROVIDER_EXISTS, + f1: true + }); + } + + async run(accessor: ServicesAccessor, ...args: any[]) { + const widgetService = accessor.get(IChatWidgetService); + const viewService = accessor.get(IViewsService); + const editorService = accessor.get(IEditorService); + + const widget = widgetService.lastFocusedWidget; + if (!widget || !('viewId' in widget.viewContext)) { + return; + } + + const viewModel = widget.viewModel; + if (!viewModel) { + return; + } + + const view = await viewService.openView(widget.viewContext.viewId) as ChatViewPane; + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { sessionId: viewModel.sessionId }, pinned: true } }); + view.clear(); + } + }); + + registerAction2(class GlobalMoveToSidebarAction extends Action2 { + constructor() { + super({ + id: `workbench.action.chat.openInSidebar`, + title: { + value: localize('interactiveSession.openInSidebar.label', "Open Session In Sidebar"), + original: 'Open Session In Sidebar' + }, + category: CHAT_CATEGORY, + precondition: CONTEXT_PROVIDER_EXISTS, + f1: true + }); + } + + async run(accessor: ServicesAccessor, ...args: any[]) { + return moveToSidebar(accessor); + } + }); +} diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index e798ba93bfd..5cc837e3bfa 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -35,6 +35,7 @@ import { ChatWidgetHistoryService, IChatWidgetHistoryService } from 'vs/workbenc import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import '../common/chatColors'; +import { registerMoveActions } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -128,6 +129,7 @@ registerChatTitleActions(); registerChatExecuteActions(); registerChatQuickQuestionActions(); registerChatExportActions(); +registerMoveActions(); registerSingleton(IChatService, ChatService, InstantiationType.Delayed); registerSingleton(IChatContributionService, ChatContributionService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts index 5490cb2e1f7..94b1d453e1e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts @@ -16,7 +16,9 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from 'vs/workbench/common/views'; -import { getClearAction, getHistoryAction, getOpenChatEditorAction } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; +import { getHistoryAction, getOpenChatEditorAction } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; +import { getClearAction } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; +import { getMoveToEditorAction, getMoveToSidebarAction } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions'; import { IChatViewOptions, CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { IChatContributionService, IChatProviderContribution, IRawChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService'; import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -136,20 +138,23 @@ export class ChatContributionService implements IChatContributionService { }]; Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, viewContainer); + // Per-provider actions + // Actions in view title - const historyAction = registerAction2(getHistoryAction(viewId, providerDescriptor.id)); - const clearAction = registerAction2(getClearAction(viewId, providerDescriptor.id)); + const disposables = new DisposableStore(); + disposables.add(registerAction2(getHistoryAction(viewId, providerDescriptor.id))); + disposables.add(registerAction2(getClearAction(viewId, providerDescriptor.id))); + disposables.add(registerAction2(getMoveToEditorAction(viewId, providerDescriptor.id))); + disposables.add(registerAction2(getMoveToSidebarAction(viewId, providerDescriptor.id))); // "Open Chat Editor" Action - const openEditor = registerAction2(getOpenChatEditorAction(providerDescriptor.id, providerDescriptor.label, providerDescriptor.when)); + disposables.add(registerAction2(getOpenChatEditorAction(providerDescriptor.id, providerDescriptor.label, providerDescriptor.when))); return { dispose: () => { Registry.as(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, viewContainer); Registry.as(ViewExtensions.ViewContainersRegistry).deregisterViewContainer(viewContainer); - clearAction.dispose(); - historyAction.dispose(); - openEditor.dispose(); + disposables.dispose(); } }; } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts index 42ed0215e03..8ec44820262 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorInput.ts @@ -88,6 +88,7 @@ export class ChatEditorInput extends EditorInput { } this.sessionId = this.model.sessionId; + this.providerId = this.model.providerId; await this.model.waitForInitialization(); this._register(this.model.onDidChange(() => this._onDidChangeLabel.fire())); diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 4bfa2aae538..5f319d4eb0e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -115,8 +115,17 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { async clear(): Promise { if (this.widget.viewModel) { this.chatService.clearSession(this.widget.viewModel.sessionId); - this.updateModel(); } + this.updateModel(); + } + + loadSession(sessionId: string): void { + if (this.widget.viewModel) { + this.chatService.clearSession(this.widget.viewModel.sessionId); + } + + const newModel = this.chatService.getOrRestoreSession(sessionId); + this.updateModel(newModel); } focusInput(): void { From 7672dba7e6a1b54cef05400a136d47ae35628d7a Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Tue, 23 May 2023 22:22:16 -0700 Subject: [PATCH 21/65] Check for onDidChangeExtensions before fetching commands (#183275) Check for onDidChangeExtensions --- .../workbench/contrib/remote/browser/remoteStartEntry.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/remoteStartEntry.ts b/src/vs/workbench/contrib/remote/browser/remoteStartEntry.ts index 94c88c45d7e..555203e5cd1 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteStartEntry.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteStartEntry.ts @@ -162,12 +162,12 @@ export class RemoteStartEntry extends Disposable implements IWorkbenchContributi } private registerListeners(): void { - this._register(this.extensionManagementService.onDidInstallExtensions(async (result) => { - for (const ext of result) { - const index = this.remoteExtensionMetadata.findIndex(value => ExtensionIdentifier.equals(value.id, ext.identifier.id)); + this._register(this.extensionService.onDidChangeExtensions(async (result) => { + for (const ext of result.added) { + const index = this.remoteExtensionMetadata.findIndex(value => ExtensionIdentifier.equals(value.id, ext.identifier)); if (index > -1) { this.remoteExtensionMetadata[index].installed = true; - this.remoteExtensionMetadata[index].remoteCommands = await this.getRemoteCommands(ext.identifier.id); + this.remoteExtensionMetadata[index].remoteCommands = await this.getRemoteCommands(ext.identifier.value); } } })); From 8625a005a6fb632b2daf3e8118248920d00888a7 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 23 May 2023 22:58:40 -0700 Subject: [PATCH 22/65] Pick up latest markdown language server (#183294) --- extensions/markdown-language-features/server/package.json | 4 ++-- extensions/markdown-language-features/server/yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/markdown-language-features/server/package.json b/extensions/markdown-language-features/server/package.json index 6a101879701..55fc5fdd847 100644 --- a/extensions/markdown-language-features/server/package.json +++ b/extensions/markdown-language-features/server/package.json @@ -1,7 +1,7 @@ { "name": "vscode-markdown-languageserver", "description": "Markdown language server", - "version": "0.4.0-alpha.1", + "version": "0.4.0-alpha.2", "author": "Microsoft Corporation", "license": "MIT", "engines": { @@ -18,7 +18,7 @@ "vscode-languageserver": "^8.1.0", "vscode-languageserver-textdocument": "^1.0.8", "vscode-languageserver-types": "^3.17.3", - "vscode-markdown-languageservice": "^0.4.0-alpha.1", + "vscode-markdown-languageservice": "^0.4.0-alpha.2", "vscode-uri": "^3.0.7" }, "devDependencies": { diff --git a/extensions/markdown-language-features/server/yarn.lock b/extensions/markdown-language-features/server/yarn.lock index 1a47c2a1746..b801eb22ebc 100644 --- a/extensions/markdown-language-features/server/yarn.lock +++ b/extensions/markdown-language-features/server/yarn.lock @@ -128,10 +128,10 @@ vscode-languageserver@^8.1.0: dependencies: vscode-languageserver-protocol "3.17.3" -vscode-markdown-languageservice@^0.4.0-alpha.1: - version "0.4.0-alpha.1" - resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.4.0-alpha.1.tgz#367582d711a95001adb1cfae2eb8c9852cc51319" - integrity sha512-MqNHwKaO5UliiJ2lNSt8FRj/68max/7/N4JwD0mLkx/NAWLXYLluBbE1CQh/Fgycpcueldk0cckKYj/qNWDtVA== +vscode-markdown-languageservice@^0.4.0-alpha.2: + version "0.4.0-alpha.2" + resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.4.0-alpha.2.tgz#2edbd157ada35922ec762a7d6550d87a0b78959a" + integrity sha512-m2x+3dezndpDqfviCzsfUgAySVhoN8266OnasPpPlIZIho3a/JcUmFo6GZDlWBtOQXd9FT+TSAC2BPDCtWlhPQ== dependencies: "@vscode/l10n" "^0.0.10" node-html-parser "^6.1.5" From 193c2f517adbab34ab882fe948d6610fbbcd3307 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 23 May 2023 23:53:57 -0700 Subject: [PATCH 23/65] Transfer SCM view mode and sort key in Continue On transition (#183127) --- .../contrib/scm/browser/scmViewPane.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 08bd68007b8..7b8f2ce8d41 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1148,7 +1148,7 @@ class ViewModel { this._onDidChangeMode.fire(mode); this.modeContextKey.set(mode); - this.storageService.store(`scm.viewMode`, mode, StorageScope.WORKSPACE, StorageTarget.MACHINE); + this.storageService.store(`scm.viewMode`, mode, StorageScope.WORKSPACE, StorageTarget.USER); } get sortKey(): ViewModelSortKey { return this._sortKey; } @@ -1164,7 +1164,7 @@ class ViewModel { this.sortKeyContextKey.set(sortKey); if (this._mode === ViewModelMode.List) { - this.storageService.store(`scm.viewSortKey`, sortKey, StorageScope.WORKSPACE, StorageTarget.MACHINE); + this.storageService.store(`scm.viewSortKey`, sortKey, StorageScope.WORKSPACE, StorageTarget.USER); } } @@ -1243,6 +1243,20 @@ class ViewModel { if (e.reason === WillSaveStateReason.SHUTDOWN) { this.storageService.store(`scm.viewState`, JSON.stringify(this.treeViewState), StorageScope.WORKSPACE, StorageTarget.MACHINE); } + + this.mode = this.getViewModelMode(); + this.sortKey = this.getViewModelSortKey(); + }); + + this.storageService.onDidChangeValue(e => { + switch (e.key) { + case 'scm.viewMode': + this.mode = this.getViewModelMode(); + break; + case 'scm.viewSortKey': + this.sortKey = this.getViewModelSortKey(); + break; + } }); } From 1e97f342a911f500dcca822185a21ed22537553d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 24 May 2023 10:35:41 +0200 Subject: [PATCH 24/65] react to diff changing and relayout inner diff (#183299) --- .../interactiveEditorLivePreviewWidget.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorLivePreviewWidget.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorLivePreviewWidget.ts index fff40debc32..5317cd1d479 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorLivePreviewWidget.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorLivePreviewWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Dimension, h } from 'vs/base/browser/dom'; -import { MutableDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; @@ -38,6 +38,7 @@ export class InteractiveEditorLivePreviewWidget extends ZoneWidget { private readonly _elements = h('div.interactive-editor-diff-widget@domNode'); + private readonly _sessionStore = this._disposables.add(new DisposableStore()); private readonly _diffEditor: IDiffEditor; private readonly _inlineDiffDecorations: IEditorDecorationsCollection; private _dim: Dimension | undefined; @@ -129,6 +130,20 @@ export class InteractiveEditorLivePreviewWidget extends ZoneWidget { override show(): void { assertType(this.editor.hasModel()); + this._sessionStore.clear(); + + this._sessionStore.add(this._diffEditor.onDidUpdateDiff(() => { + const result = this._diffEditor.getDiffComputationResult(); + const hasFocus = this._diffEditor.hasTextFocus(); + this._updateFromChanges(this._session.wholeRange, result?.changes2 ?? []); + // TODO@jrieken find a better fix for this. this is the challenge: + // the _doShowForChanges method invokes show of the zone widget which removes and adds the + // zone and overlay parts. this dettaches and reattaches the dom nodes which means they lose + // focus + if (hasFocus) { + this._diffEditor.focus(); + } + })); this._updateFromChanges(this._session.wholeRange, this._session.lastTextModelChanges); this._isVisible = true; } From 3bd5d0c64bd3dbb587065c27aa05ad4b185cbf5f Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Wed, 24 May 2023 09:52:41 +0200 Subject: [PATCH 25/65] =?UTF-8?q?=F0=9F=92=84=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contrib/preferences/browser/keybindingWidgets.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index 020ecc21eb7..35e71101940 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -59,7 +59,9 @@ export class KeybindingsSearchWidget extends SearchWidget { @IKeybindingService keybindingService: IKeybindingService, ) { super(parent, options, contextViewService, instantiationService, contextKeyService, keybindingService); + this._register(toDisposable(() => this.stopRecordingKeys())); + this._chords = null; this._inputValue = ''; @@ -247,19 +249,15 @@ export class DefineKeybindingWidget extends Widget { dom.clearNode(this._outputNode); dom.clearNode(this._showExistingKeybindingsNode); - - const firstLabel = new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles); firstLabel.set(withNullAsUndefined(this._chords?.[0])); - if (this._chords) { for (let i = 1; i < this._chords.length; i++) { this._outputNode.appendChild(document.createTextNode(nls.localize('defineKeybinding.chordsTo', "chord to"))); const chordLabel = new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles); chordLabel.set(this._chords[i]); } - } const label = this.getUserSettingsLabel(); From 49043016b5a988dc72dd945dd8f40633e3617de6 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 24 May 2023 10:51:07 +0200 Subject: [PATCH 26/65] cleaning the code --- .../src/languageFeatures/quickFix.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 076ad1a5da4..9d5ba9fdef2 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -324,15 +324,14 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { - - const findScopeEndLine = (startLine: number, navigationTree: Proto.NavigationTree[]): number => { + const findScopeEndLineFromNavTree = (startLine: number, navigationTree: Proto.NavigationTree[]): number => { for (const node of navigationTree) { const nodeStartLine = node.spans[0].start.line; const nodeEndLine = node.spans[0].end.line; - if (nodeStartLine === startLine) { + if (startLine === nodeStartLine) { return nodeEndLine; } else if (startLine > nodeStartLine && startLine <= nodeEndLine && node.childItems) { - return findScopeEndLine(startLine, node.childItems); + return findScopeEndLineFromNavTree(startLine, node.childItems); } } return -1; @@ -346,7 +345,7 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider Date: Wed, 24 May 2023 10:53:08 +0200 Subject: [PATCH 27/65] KeybindingsSearchWidget: refactor: inline _reset() because it doesn't bring much value --- .../contrib/preferences/browser/keybindingWidgets.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index 35e71101940..fd382e3a31c 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -64,12 +64,10 @@ export class KeybindingsSearchWidget extends SearchWidget { this._chords = null; this._inputValue = ''; - - this._reset(); } override clear(): void { - this._reset(); + this._chords = null; super.clear(); } @@ -83,7 +81,7 @@ export class KeybindingsSearchWidget extends SearchWidget { } stopRecordingKeys(): void { - this._reset(); + this._chords = null; this.recordDisposables.clear(); } @@ -92,10 +90,6 @@ export class KeybindingsSearchWidget extends SearchWidget { this.inputBox.value = this._inputValue; } - private _reset() { - this._chords = null; - } - private _onKeyDown(keyboardEvent: IKeyboardEvent): void { keyboardEvent.preventDefault(); keyboardEvent.stopPropagation(); From edcad3ab531be6b4cb72ac5cec3678fb88f87c4a Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 24 May 2023 11:15:17 +0200 Subject: [PATCH 28/65] GitHub - use GitHub default branch name when publishing to GitHub (#183207) --- extensions/git/src/api/api1.ts | 6 +++--- extensions/git/src/api/git.d.ts | 6 +++++- extensions/git/src/git.ts | 4 ++-- extensions/github/src/publish.ts | 2 +- extensions/github/src/typings/git.d.ts | 6 +++++- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index e1fcc022aa6..143d23148f1 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -5,7 +5,7 @@ import { Model } from '../model'; import { Repository as BaseRepository, Resource } from '../repository'; -import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider } from './git'; +import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions } from './git'; import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode'; import { combinedDisposable, mapEvent } from '../util'; import { toGitUri } from '../uri'; @@ -294,9 +294,9 @@ export class ApiImpl implements API { return result ? new ApiRepository(result) : null; } - async init(root: Uri): Promise { + async init(root: Uri, options?: InitOptions): Promise { const path = root.fsPath; - await this._model.git.init(path); + await this._model.git.init(path, options); await this._model.openRepository(path); return this.getRepository(root) || null; } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index e3ec7c1000d..7c7bfcefd4c 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -156,6 +156,10 @@ export interface FetchOptions { depth?: number; } +export interface InitOptions { + defaultBranch?: string; +} + export interface RefQuery { readonly contains?: string; readonly count?: number; @@ -307,7 +311,7 @@ export interface API { toGitUri(uri: Uri, ref: string): Uri; getRepository(uri: Uri): Repository | null; - init(root: Uri): Promise; + init(root: Uri, options?: InitOptions): Promise; openRepository(root: Uri): Promise registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable; diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 1fe5a80dba4..2bf9ec4c47f 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -15,7 +15,7 @@ import * as filetype from 'file-type'; import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows, pathEquals } from './util'; import { CancellationError, CancellationToken, ConfigurationChangeEvent, LogOutputChannel, Progress, Uri, workspace } from 'vscode'; import { detectEncoding } from './encoding'; -import { Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery } from './api/git'; +import { Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery, InitOptions } from './api/git'; import * as byline from 'byline'; import { StringDecoder } from 'string_decoder'; @@ -401,7 +401,7 @@ export class Git { return new Repository(this, repository, dotGit, logger); } - async init(repository: string, options: { defaultBranch?: string } = {}): Promise { + async init(repository: string, options: InitOptions = {}): Promise { const args = ['init']; if (options.defaultBranch && options.defaultBranch !== '') { diff --git a/extensions/github/src/publish.ts b/extensions/github/src/publish.ts index 3f24b1061cc..dee8898d348 100644 --- a/extensions/github/src/publish.ts +++ b/extensions/github/src/publish.ts @@ -190,7 +190,7 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) progress.report({ message: vscode.l10n.t('Creating first commit'), increment: 25 }); if (!repository) { - repository = await gitAPI.init(folder) || undefined; + repository = await gitAPI.init(folder, { defaultBranch: createdGithubRepository.default_branch }) || undefined; if (!repository) { return; diff --git a/extensions/github/src/typings/git.d.ts b/extensions/github/src/typings/git.d.ts index 83b00f324c0..2470ddfbe99 100644 --- a/extensions/github/src/typings/git.d.ts +++ b/extensions/github/src/typings/git.d.ts @@ -156,6 +156,10 @@ export interface FetchOptions { depth?: number; } +export interface InitOptions { + defaultBranch?: string; +} + export interface BranchQuery { readonly remote?: boolean; readonly pattern?: string; @@ -301,7 +305,7 @@ export interface API { toGitUri(uri: Uri, ref: string): Uri; getRepository(uri: Uri): Repository | null; - init(root: Uri): Promise; + init(root: Uri, options?: InitOptions): Promise; openRepository(root: Uri): Promise registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable; From 9f081fd11a036678d0453c5820554c27dc39173e Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 24 May 2023 02:23:40 -0700 Subject: [PATCH 29/65] Open branches on vscode.dev from ref picker (#181549) --- extensions/git-base/src/api/api1.ts | 8 ++- extensions/git-base/src/api/git-base.d.ts | 10 +++ extensions/git-base/src/remoteSource.ts | 16 ++++- extensions/git/src/api/git-base.d.ts | 28 +++++++- extensions/git/src/commands.ts | 71 +++++++++++++++++-- extensions/git/src/remoteSource.ts | 4 ++ extensions/github/src/commands.ts | 6 +- extensions/github/src/links.ts | 14 ++++ extensions/github/src/remoteSourceProvider.ts | 28 +++++++- extensions/github/src/typings/git-base.d.ts | 10 +++ 10 files changed, 179 insertions(+), 16 deletions(-) diff --git a/extensions/git-base/src/api/api1.ts b/extensions/git-base/src/api/api1.ts index 7b261f10683..005a7930356 100644 --- a/extensions/git-base/src/api/api1.ts +++ b/extensions/git-base/src/api/api1.ts @@ -5,9 +5,9 @@ import { Disposable, commands } from 'vscode'; import { Model } from '../model'; -import { pickRemoteSource } from '../remoteSource'; +import { getRemoteSourceActions, pickRemoteSource } from '../remoteSource'; import { GitBaseExtensionImpl } from './extension'; -import { API, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceProvider } from './git-base'; +import { API, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceAction, RemoteSourceProvider } from './git-base'; export class ApiImpl implements API { @@ -17,6 +17,10 @@ export class ApiImpl implements API { return pickRemoteSource(this._model, options as any); } + getRemoteSourceActions(url: string): Promise { + return getRemoteSourceActions(this._model, url); + } + registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable { return this._model.registerRemoteSourceProvider(provider); } diff --git a/extensions/git-base/src/api/git-base.d.ts b/extensions/git-base/src/api/git-base.d.ts index 8510df6d043..53cac4d5c70 100644 --- a/extensions/git-base/src/api/git-base.d.ts +++ b/extensions/git-base/src/api/git-base.d.ts @@ -44,6 +44,15 @@ export interface PickRemoteSourceResult { readonly branch?: string; } +export interface RemoteSourceAction { + readonly label: string; + /** + * Codicon name + */ + readonly icon: string; + run(branch: string): void; +} + export interface RemoteSource { readonly name: string; readonly description?: string; @@ -70,6 +79,7 @@ export interface RemoteSourceProvider { readonly supportsQuery?: boolean; getBranches?(url: string): ProviderResult; + getRemoteSourceActions?(url: string): ProviderResult; getRecentRemoteSources?(query?: string): ProviderResult; getRemoteSources(query?: string): ProviderResult; } diff --git a/extensions/git-base/src/remoteSource.ts b/extensions/git-base/src/remoteSource.ts index d7fb85c620f..05831eb010b 100644 --- a/extensions/git-base/src/remoteSource.ts +++ b/extensions/git-base/src/remoteSource.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { QuickPickItem, window, QuickPick, QuickPickItemKind, l10n } from 'vscode'; -import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult } from './api/git-base'; +import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceAction } from './api/git-base'; import { Model } from './model'; import { throttle, debounce } from './decorators'; @@ -81,6 +81,20 @@ class RemoteSourceProviderQuickPick { } } +export async function getRemoteSourceActions(model: Model, url: string): Promise { + const providers = model.getRemoteProviders(); + + const remoteSourceActions = []; + for (const provider of providers) { + const providerActions = await provider.getRemoteSourceActions?.(url); + if (providerActions?.length) { + remoteSourceActions.push(...providerActions); + } + } + + return remoteSourceActions; +} + export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch?: false | undefined }): Promise; export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch: true }): Promise; export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise { diff --git a/extensions/git/src/api/git-base.d.ts b/extensions/git/src/api/git-base.d.ts index dca68d13071..1eeb1739901 100644 --- a/extensions/git/src/api/git-base.d.ts +++ b/extensions/git/src/api/git-base.d.ts @@ -8,6 +8,7 @@ export { ProviderResult } from 'vscode'; export interface API { pickRemoteSource(options: PickRemoteSourceOptions): Promise; + getRemoteSourceActions(url: string): Promise; registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable; } @@ -31,9 +32,12 @@ export interface GitBaseExtension { export interface PickRemoteSourceOptions { readonly providerLabel?: (provider: RemoteSourceProvider) => string; - readonly urlLabel?: string; + readonly urlLabel?: string | ((url: string) => string); readonly providerName?: string; + readonly title?: string; + readonly placeholder?: string; readonly branch?: boolean; // then result is PickRemoteSourceResult + readonly showRecentSources?: boolean; } export interface PickRemoteSourceResult { @@ -41,20 +45,42 @@ export interface PickRemoteSourceResult { readonly branch?: string; } +export interface RemoteSourceAction { + readonly label: string; + /** + * Codicon name + */ + readonly icon: string; + run(branch: string): void; +} + export interface RemoteSource { readonly name: string; readonly description?: string; + readonly detail?: string; + /** + * Codicon name + */ + readonly icon?: string; readonly url: string | string[]; } +export interface RecentRemoteSource extends RemoteSource { + readonly timestamp: number; +} + export interface RemoteSourceProvider { readonly name: string; /** * Codicon name */ readonly icon?: string; + readonly label?: string; + readonly placeholder?: string; readonly supportsQuery?: boolean; getBranches?(url: string): ProviderResult; + getRemoteSourceActions?(url: string): ProviderResult; + getRecentRemoteSources?(query?: string): ProviderResult; getRemoteSources(query?: string): ProviderResult; } diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 624e9d2353c..cdf81dc6e61 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,7 +5,7 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind } from 'vscode'; +import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { Branch, ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git'; @@ -17,7 +17,8 @@ import { fromGitUri, toGitUri, isGitUri, toMergeUris } from './uri'; import { grep, isDescendant, pathEquals, relativePath } from './util'; import { GitTimelineItem } from './timelineProvider'; import { ApiRepository } from './api/api1'; -import { pickRemoteSource } from './remoteSource'; +import { getRemoteSourceActions, pickRemoteSource } from './remoteSource'; +import { RemoteSourceAction } from './api/git-base'; class CheckoutItem implements QuickPickItem { @@ -25,8 +26,11 @@ class CheckoutItem implements QuickPickItem { get label(): string { return `${this.repository.isBranchProtected(this.ref) ? '$(lock)' : '$(git-branch)'} ${this.ref.name || this.shortCommit}`; } get description(): string { return this.shortCommit; } get refName(): string | undefined { return this.ref.name; } + get refRemote(): string | undefined { return this.ref.remote; } + get buttons(): QuickInputButton[] | undefined { return this._buttons; } + set buttons(newButtons: QuickInputButton[] | undefined) { this._buttons = newButtons; } - constructor(protected repository: Repository, protected ref: Ref) { } + constructor(protected repository: Repository, protected ref: Ref, protected _buttons?: QuickInputButton[]) { } async run(opts?: { detached?: boolean }): Promise { if (!this.ref.name) { @@ -278,7 +282,54 @@ async function createCheckoutItems(repository: Repository, detached = false): Pr } } - return processors.reduce((r, p) => r.concat(...p.items), []); + const buttons = await getRemoteRefItemButtons(repository); + let fallbackRemoteButtons: RemoteSourceActionButton[] | undefined = []; + const remote = repository.remotes.find(r => r.pushUrl === repository.HEAD?.remote || r.fetchUrl === repository.HEAD?.remote) ?? repository.remotes[0]; + const remoteUrl = remote.pushUrl ?? remote.fetchUrl; + if (remoteUrl) { + fallbackRemoteButtons = buttons.get(remoteUrl); + } + + return processors.reduce((r, p) => r.concat(...p.items.map((item) => { + if (item.refRemote) { + const matchingRemote = repository.remotes.find((remote) => remote.name === item.refRemote); + const remoteUrl = matchingRemote?.pushUrl ?? matchingRemote?.fetchUrl; + if (remoteUrl) { + item.buttons = buttons.get(item.refRemote); + } + } + + item.buttons = fallbackRemoteButtons; + return item; + })), []); +} + +type RemoteSourceActionButton = { + iconPath: ThemeIcon; + tooltip: string; + actual: RemoteSourceAction; +}; + +async function getRemoteRefItemButtons(repository: Repository) { + // Compute actions for all known remotes + const remoteUrlsToActions = new Map(); + + const getButtons = async (remoteUrl: string) => (await getRemoteSourceActions(remoteUrl)).map((action) => ({ iconPath: new ThemeIcon(action.icon), tooltip: action.label, actual: action })); + + for (const remote of repository.remotes) { + if (remote.fetchUrl) { + const actions = remoteUrlsToActions.get(remote.fetchUrl) ?? []; + actions.push(...await getButtons(remote.fetchUrl)); + remoteUrlsToActions.set(remote.fetchUrl, actions); + } + if (remote.pushUrl && remote.pushUrl !== remote.fetchUrl) { + const actions = remoteUrlsToActions.get(remote.pushUrl) ?? []; + actions.push(...await getButtons(remote.pushUrl)); + remoteUrlsToActions.set(remote.pushUrl, actions); + } + } + + return remoteUrlsToActions; } class CheckoutProcessor { @@ -2084,7 +2135,17 @@ export class CommandCenter { quickpick.items = picks; quickpick.busy = false; - const choice = await new Promise(c => quickpick.onDidAccept(() => c(quickpick.activeItems[0]))); + const choice = await new Promise(c => { + quickpick.onDidAccept(() => c(quickpick.activeItems[0])); + quickpick.onDidTriggerItemButton((e) => { + quickpick.hide(); + const button = e.button as QuickInputButton & { actual: RemoteSourceAction }; + const item = e.item as CheckoutItem; + if (button.actual && item.refName) { + button.actual.run(item.refRemote ? item.refName.substring(item.refRemote.length + 1) : item.refName); + } + }); + }); quickpick.hide(); if (!choice) { diff --git a/extensions/git/src/remoteSource.ts b/extensions/git/src/remoteSource.ts index 4f62181f00c..4fdd6f06c1d 100644 --- a/extensions/git/src/remoteSource.ts +++ b/extensions/git/src/remoteSource.ts @@ -11,3 +11,7 @@ export async function pickRemoteSource(options: PickRemoteSourceOptions & { bran export async function pickRemoteSource(options: PickRemoteSourceOptions = {}): Promise { return GitBaseApi.getAPI().pickRemoteSource(options); } + +export async function getRemoteSourceActions(url: string) { + return GitBaseApi.getAPI().getRemoteSourceActions(url); +} diff --git a/extensions/github/src/commands.ts b/extensions/github/src/commands.ts index 8a653e523c4..11b1d3d649a 100644 --- a/extensions/github/src/commands.ts +++ b/extensions/github/src/commands.ts @@ -7,11 +7,7 @@ import * as vscode from 'vscode'; import { API as GitAPI } from './typings/git'; import { publishRepository } from './publish'; import { DisposableStore } from './util'; -import { LinkContext, getLink } from './links'; - -function getVscodeDevHost(): string { - return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`; -} +import { LinkContext, getLink, getVscodeDevHost } from './links'; async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean, context: LinkContext, includeRange = true) { try { diff --git a/extensions/github/src/links.ts b/extensions/github/src/links.ts index 223da3712fe..97fb3218a2a 100644 --- a/extensions/github/src/links.ts +++ b/extensions/github/src/links.ts @@ -168,3 +168,17 @@ export function getLink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: stri return `${hostPrefix}/${repo.owner}/${repo.repo}${blobSegment }${fileSegments}`; } + +export function getBranchLink(url: string, branch: string, hostPrefix: string = 'https://github.com') { + const repo = getRepositoryFromUrl(url); + if (!repo) { + throw new Error('Invalid repository URL provided'); + } + + branch = encodeURIComponentExceptSlashes(branch); + return `${hostPrefix}/${repo.owner}/${repo.repo}/tree/${branch}`; +} + +export function getVscodeDevHost(): string { + return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`; +} diff --git a/extensions/github/src/remoteSourceProvider.ts b/extensions/github/src/remoteSourceProvider.ts index e8eeb851549..0d8b9340695 100644 --- a/extensions/github/src/remoteSourceProvider.ts +++ b/extensions/github/src/remoteSourceProvider.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { workspace } from 'vscode'; -import { RemoteSourceProvider, RemoteSource } from './typings/git-base'; +import { Uri, env, l10n, workspace } from 'vscode'; +import { RemoteSourceProvider, RemoteSource, RemoteSourceAction } from './typings/git-base'; import { getOctokit } from './auth'; import { Octokit } from '@octokit/rest'; import { getRepositoryFromQuery, getRepositoryFromUrl } from './util'; +import { getBranchLink, getVscodeDevHost } from './links'; function asRemoteSource(raw: any): RemoteSource { const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol'); @@ -112,4 +113,27 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider { return branches.sort((a, b) => a === defaultBranch ? -1 : b === defaultBranch ? 1 : 0); } + + async getRemoteSourceActions(url: string): Promise { + const repository = getRepositoryFromUrl(url); + if (!repository) { + return []; + } + + return [{ + label: l10n.t('Open on GitHub'), + icon: 'github', + run(branch: string) { + const link = getBranchLink(url, branch); + env.openExternal(Uri.parse(link)); + } + }, { + label: l10n.t('Checkout on vscode.dev'), + icon: 'globe', + run(branch: string) { + const link = getBranchLink(url, branch, getVscodeDevHost()); + env.openExternal(Uri.parse(link)); + } + }]; + } } diff --git a/extensions/github/src/typings/git-base.d.ts b/extensions/github/src/typings/git-base.d.ts index 8510df6d043..53cac4d5c70 100644 --- a/extensions/github/src/typings/git-base.d.ts +++ b/extensions/github/src/typings/git-base.d.ts @@ -44,6 +44,15 @@ export interface PickRemoteSourceResult { readonly branch?: string; } +export interface RemoteSourceAction { + readonly label: string; + /** + * Codicon name + */ + readonly icon: string; + run(branch: string): void; +} + export interface RemoteSource { readonly name: string; readonly description?: string; @@ -70,6 +79,7 @@ export interface RemoteSourceProvider { readonly supportsQuery?: boolean; getBranches?(url: string): ProviderResult; + getRemoteSourceActions?(url: string): ProviderResult; getRecentRemoteSources?(query?: string): ProviderResult; getRemoteSources(query?: string): ProviderResult; } From 8e705be0e13c631bf31f31d866bd3e8dbc4743b5 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Wed, 24 May 2023 11:31:39 +0200 Subject: [PATCH 30/65] define-keybinding widget: escape clears if non-empty, otherwise hides the widget --- .../preferences/browser/keybindingWidgets.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index fd382e3a31c..c8e4a1fb3c0 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -177,7 +177,7 @@ export class DefineKeybindingWidget extends Widget { this._keybindingInputWidget.startRecordingKeys(); this._register(this._keybindingInputWidget.onKeybinding(keybinding => this.onKeybinding(keybinding))); this._register(this._keybindingInputWidget.onEnter(() => this.hide())); - this._register(this._keybindingInputWidget.onEscape(() => this.onCancel())); + this._register(this._keybindingInputWidget.onEscape(() => this.clearOrHide())); this._register(this._keybindingInputWidget.onBlur(() => this.onCancel())); this._outputNode = dom.append(this._domNode.domNode, dom.$('.output')); @@ -273,6 +273,17 @@ export class DefineKeybindingWidget extends Widget { this.hide(); } + private clearOrHide(): void { + if (this._chords === null) { + this.hide(); + } else { + this._chords = null; + this._keybindingInputWidget.clear(); + dom.clearNode(this._outputNode); + dom.clearNode(this._showExistingKeybindingsNode); + } + } + private hide(): void { this._domNode.setDisplay('none'); this._isVisible = false; From 73fcc6bdc832a30189a8a68889c28acd79d76f0e Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 24 May 2023 12:27:01 +0200 Subject: [PATCH 31/65] PortAttributesSelector rename + remove pid (#183304) Part of #115616 --- .../api/browser/mainThreadTunnelService.ts | 9 ++++----- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../workbench/api/common/extHost.protocol.ts | 5 ++--- .../api/common/extHostTunnelService.ts | 6 +++--- .../workbench/api/node/extHostTunnelService.ts | 6 +++--- .../vscode.proposed.portsAttributes.d.ts | 18 ++++++++---------- 6 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index 846d68c2d14..ab5fabffdc2 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { MainThreadTunnelServiceShape, MainContext, ExtHostContext, ExtHostTunnelServiceShape, CandidatePortSource, PortAttributesProviderSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol'; +import { MainThreadTunnelServiceShape, MainContext, ExtHostContext, ExtHostTunnelServiceShape, CandidatePortSource, PortAttributesSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol'; import { TunnelDtoConverter } from 'vs/workbench/api/common/extHostTunnelService'; import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; import { CandidatePort, IRemoteExplorerService, makeAddress, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, TunnelCloseReason, TunnelSource } from 'vs/workbench/services/remote/common/remoteExplorerService'; @@ -23,7 +23,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'v export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape, PortAttributesProvider { private readonly _proxy: ExtHostTunnelServiceShape; private elevateionRetry: boolean = false; - private portsAttributesProviders: Map = new Map(); + private portsAttributesProviders: Map = new Map(); constructor( extHostContext: IExtHostContext, @@ -63,7 +63,7 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun } private _alreadyRegistered: boolean = false; - async $registerPortsAttributesProvider(selector: PortAttributesProviderSelector, providerHandle: number): Promise { + async $registerPortsAttributesProvider(selector: PortAttributesSelector, providerHandle: number): Promise { this.portsAttributesProviders.set(providerHandle, selector); if (!this._alreadyRegistered) { this.remoteExplorerService.tunnelModel.addAttributesProvider(this); @@ -85,9 +85,8 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun const selector = entry[1]; const portRange = selector.portRange; const portInRange = portRange ? ports.some(port => portRange[0] <= port && port < portRange[1]) : true; - const pidMatches = !selector.pid || (selector.pid === pid); const commandMatches = !selector.commandPattern || (commandLine && (commandLine.match(selector.commandPattern))); - return portInRange && pidMatches && commandMatches; + return portInRange && commandMatches; }).map(entry => entry[0]); if (appropriateHandles.length === 0) { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index e6f76b539d0..00ff9e2cff7 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1077,7 +1077,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'tunnels'); return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables); }, - registerPortAttributesProvider: (portSelector: { pid?: number; portRange?: [number, number]; commandPattern?: RegExp }, provider: vscode.PortAttributesProvider) => { + registerPortAttributesProvider: (portSelector: vscode.PortAttributesSelector, provider: vscode.PortAttributesProvider) => { checkProposedApiEnabled(extension, 'portsAttributes'); return extHostTunnelService.registerPortsAttributesProvider(portSelector, provider); }, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index a689fa33976..438fa03cd44 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1420,8 +1420,7 @@ export enum CandidatePortSource { Output = 2 } -export interface PortAttributesProviderSelector { - pid?: number; +export interface PortAttributesSelector { portRange?: [number, number]; commandPattern?: RegExp; } @@ -1435,7 +1434,7 @@ export interface MainThreadTunnelServiceShape extends IDisposable { $setCandidateFilter(): Promise; $onFoundNewCandidates(candidates: CandidatePort[]): Promise; $setCandidatePortSource(source: CandidatePortSource): Promise; - $registerPortsAttributesProvider(selector: PortAttributesProviderSelector, providerHandle: number): Promise; + $registerPortsAttributesProvider(selector: PortAttributesSelector, providerHandle: number): Promise; $unregisterPortsAttributesProvider(providerHandle: number): Promise; } diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index a6ad6135611..dfdfc5ab5a2 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtHostTunnelServiceShape, TunnelDto } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostTunnelServiceShape, PortAttributesSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import * as vscode from 'vscode'; import { ProvidedPortAttributes, RemoteTunnel, TunnelCreationOptions, TunnelOptions, TunnelPrivacyId } from 'vs/platform/tunnel/common/tunnel'; @@ -48,7 +48,7 @@ export interface IExtHostTunnelService extends ExtHostTunnelServiceShape { getTunnels(): Promise; onDidChangeTunnels: vscode.Event; setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise; - registerPortsAttributesProvider(portSelector: { pid?: number; portRange?: [number, number]; commandPattern?: RegExp }, provider: vscode.PortAttributesProvider): IDisposable; + registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider): IDisposable; } export const IExtHostTunnelService = createDecorator('IExtHostTunnelService'); @@ -74,7 +74,7 @@ export class ExtHostTunnelService implements IExtHostTunnelService { async setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise { return { dispose: () => { } }; } - registerPortsAttributesProvider(portSelector: { pid?: number; portRange?: [number, number] }, provider: vscode.PortAttributesProvider) { + registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider) { return { dispose: () => { } }; } diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index 209c4623e92..ae3cf3568f8 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { MainThreadTunnelServiceShape, MainContext, PortAttributesProviderSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol'; +import { MainThreadTunnelServiceShape, MainContext, PortAttributesSelector, TunnelDto } from 'vs/workbench/api/common/extHost.protocol'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import type * as vscode from 'vscode'; import * as nls from 'vs/nls'; @@ -186,7 +186,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe private _initialCandidates: CandidatePort[] | undefined = undefined; private _providerHandleCounter: number = 0; - private _portAttributesProviders: Map = new Map(); + private _portAttributesProviders: Map = new Map(); constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, @@ -232,7 +232,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe return this._providerHandleCounter++; } - registerPortsAttributesProvider(portSelector: PortAttributesProviderSelector, provider: vscode.PortAttributesProvider): vscode.Disposable { + registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider): vscode.Disposable { const providerHandle = this.nextPortAttributesProviderHandle(); this._portAttributesProviders.set(providerHandle, { selector: portSelector, provider }); diff --git a/src/vscode-dts/vscode.proposed.portsAttributes.d.ts b/src/vscode-dts/vscode.proposed.portsAttributes.d.ts index 6f07fe7aadf..cf130357ead 100644 --- a/src/vscode-dts/vscode.proposed.portsAttributes.d.ts +++ b/src/vscode-dts/vscode.proposed.portsAttributes.d.ts @@ -7,6 +7,9 @@ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/115616 @alexr00 + /** + * The action that should be taken when a port is discovered through automatic port forwarding discovery. + */ export enum PortAutoForwardAction { /** * Notify the user that the port is being forwarded. This is the default action. @@ -67,21 +70,16 @@ declare module 'vscode' { providePortAttributes(port: number, pid: number | undefined, commandLine: string | undefined, token: CancellationToken): ProviderResult; } - export interface PortAttributesProviderSelector { - /** - * TODO: @alexr00 no one is currently using this. Should we delete it? - * If your {@link PortAttributesProvider PortAttributesProvider} is registered after your process has started then already know the process id of port you are listening on. - * Specifying a pid will cause your provider to only be called for ports that match the pid. - */ - pid?: number; - + /** + * A selector that will be used to filter which {@link PortAttributesProvider} should be called for each port. + */ + export interface PortAttributesSelector { /** * Specifying a port range will cause your provider to only be called for ports within the range. */ portRange?: [number, number]; /** - * TODO: @alexr00 no one is currently using this. Should we delete it? * Specifying a command pattern will cause your provider to only be called for processes whose command line matches the pattern. */ commandPattern?: RegExp; @@ -100,6 +98,6 @@ declare module 'vscode' { * If you don't specify a port selector your provider will be called for every port, which will result in slower port forwarding for the user. * @param provider The {@link PortAttributesProvider PortAttributesProvider}. */ - export function registerPortAttributesProvider(portSelector: PortAttributesProviderSelector, provider: PortAttributesProvider): Disposable; + export function registerPortAttributesProvider(portSelector: PortAttributesSelector, provider: PortAttributesProvider): Disposable; } } From 36464655d16d4b26df0f3ec913d9f9db626eafad Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 24 May 2023 12:59:49 +0200 Subject: [PATCH 32/65] update EH side status bar info as new extensions are registered (#183309) https://github.com/microsoft/vscode/issues/167874 --- .../api/browser/mainThreadStatusBar.ts | 20 +++++++++++---- .../api/browser/statusBarExtensionPoint.ts | 25 +++++++++++++++++-- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadStatusBar.ts b/src/vs/workbench/api/browser/mainThreadStatusBar.ts index 21166883d5a..faf4ca55bf8 100644 --- a/src/vs/workbench/api/browser/mainThreadStatusBar.ts +++ b/src/vs/workbench/api/browser/mainThreadStatusBar.ts @@ -11,7 +11,7 @@ import { Command } from 'vs/editor/common/languages'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IExtensionStatusBarItemService } from 'vs/workbench/api/browser/statusBarExtensionPoint'; -import { StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; +import { IStatusbarEntry, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar'; @extHostNamedCustomer(MainContext.MainThreadStatusBar) export class MainThreadStatusBar implements MainThreadStatusBarShape { @@ -27,17 +27,27 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { // once, at startup read existing items and send them over const entries: StatusBarItemDto[] = []; for (const [entryId, item] of statusbarService.getEntries()) { - entries.push({ + entries.push(asDto(entryId, item)); + } + + proxy.$acceptStaticEntries(entries); + + statusbarService.onDidChange(e => { + if (e.added) { + proxy.$acceptStaticEntries([asDto(e.added[0], e.added[1])]); + } + }); + + function asDto(entryId: string, item: { entry: IStatusbarEntry; alignment: StatusbarAlignment; priority: number }): StatusBarItemDto { + return { entryId, name: item.entry.name, text: item.entry.text, command: typeof item.entry.command === 'string' ? item.entry.command : undefined, priority: item.priority, alignLeft: item.alignment === StatusbarAlignment.LEFT - }); + }; } - - proxy.$acceptStaticEntries(entries); } dispose(): void { diff --git a/src/vs/workbench/api/browser/statusBarExtensionPoint.ts b/src/vs/workbench/api/browser/statusBarExtensionPoint.ts index 9cf4e62872f..48f91f7ffbe 100644 --- a/src/vs/workbench/api/browser/statusBarExtensionPoint.ts +++ b/src/vs/workbench/api/browser/statusBarExtensionPoint.ts @@ -16,6 +16,7 @@ import { IAccessibilityInformation } from 'vs/platform/accessibility/common/acce import { IMarkdownString } from 'vs/base/common/htmlContent'; import { getCodiconAriaLabel } from 'vs/base/common/iconLabels'; import { hash } from 'vs/base/common/hash'; +import { Event, Emitter } from 'vs/base/common/event'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Iterable } from 'vs/base/common/iterator'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -27,16 +28,24 @@ import { asStatusBarItemIdentifier } from 'vs/workbench/api/common/extHostTypes' export const IExtensionStatusBarItemService = createDecorator('IExtensionStatusBarItemService'); export interface IExtensionStatusBarItemChangeEvent { - readonly added?: Readonly<{ entryId: string } & IStatusbarEntry>; + readonly added?: ExtensionStatusBarEntry; readonly removed?: string; } +export type ExtensionStatusBarEntry = [string, { + entry: IStatusbarEntry; + alignment: MainThreadStatusBarAlignment; + priority: number; +}]; + export interface IExtensionStatusBarItemService { readonly _serviceBrand: undefined; + onDidChange: Event; + setOrUpdateEntry(id: string, statusId: string, extensionId: string | undefined, name: string, text: string, tooltip: IMarkdownString | string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, backgroundColor: string | ThemeColor | undefined, alignLeft: boolean, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): IDisposable; - getEntries(): Iterable<[string, { entry: IStatusbarEntry; alignment: MainThreadStatusBarAlignment; priority: number }]>; + getEntries(): Iterable; } @@ -46,8 +55,17 @@ class ExtensionStatusBarItemService implements IExtensionStatusBarItemService { private readonly _entries: Map = new Map(); + private readonly _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; + constructor(@IStatusbarService private readonly _statusbarService: IStatusbarService) { } + dispose(): void { + this._entries.forEach(entry => entry.accessor.dispose()); + this._entries.clear(); + this._onDidChange.dispose(); + } + setOrUpdateEntry(entryId: string, id: string, extensionId: string | undefined, name: string, text: string, tooltip: IMarkdownString | string | undefined, command: Command | undefined, color: string | ThemeColor | undefined, backgroundColor: string | ThemeColor | undefined, alignLeft: boolean, priority: number | undefined, accessibilityInformation: IAccessibilityInformation | undefined): IDisposable { // if there are icons in the text use the tooltip for the aria label let ariaLabel: string; @@ -98,6 +116,8 @@ class ExtensionStatusBarItemService implements IExtensionStatusBarItemService { priority }); + this._onDidChange.fire({ added: [entryId, { entry, alignment, priority }] }); + } else { // Otherwise update existingEntry.accessor.update(entry); @@ -109,6 +129,7 @@ class ExtensionStatusBarItemService implements IExtensionStatusBarItemService { if (entry) { entry.accessor.dispose(); this._entries.delete(entryId); + this._onDidChange.fire({ removed: entryId }); } }); } From e7e97f8ea8519885e13dac9df6eae7c747d531b1 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 24 May 2023 13:11:57 +0200 Subject: [PATCH 33/65] Git - handle --intent-to-add on a renamed resource (#183307) --- extensions/git/src/api/api1.ts | 1 + extensions/git/src/api/git.d.ts | 1 + extensions/git/src/decorationProvider.ts | 2 +- extensions/git/src/git.ts | 2 +- extensions/git/src/repository.ts | 15 +++++++++++++-- extensions/github/src/typings/git.d.ts | 1 + 6 files changed, 18 insertions(+), 4 deletions(-) diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 143d23148f1..375d07c4198 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -362,6 +362,7 @@ function getStatus(status: Status): string { case Status.UNTRACKED: return 'UNTRACKED'; case Status.IGNORED: return 'IGNORED'; case Status.INTENT_TO_ADD: return 'INTENT_TO_ADD'; + case Status.INTENT_TO_RENAME: return 'INTENT_TO_RENAME'; case Status.ADDED_BY_US: return 'ADDED_BY_US'; case Status.ADDED_BY_THEM: return 'ADDED_BY_THEM'; case Status.DELETED_BY_US: return 'DELETED_BY_US'; diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index 7c7bfcefd4c..30b8c271103 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -78,6 +78,7 @@ export const enum Status { UNTRACKED, IGNORED, INTENT_TO_ADD, + INTENT_TO_RENAME, ADDED_BY_US, ADDED_BY_THEM, diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index 7c8ee74043c..c630f00c712 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -131,7 +131,7 @@ class GitDecorationProvider implements FileDecorationProvider { bucket.set(r.rightUri.toString(), decoration); } - if (r.type === Status.INDEX_RENAMED) { + if (r.type === Status.INDEX_RENAMED || r.type === Status.INTENT_TO_RENAME) { bucket.set(r.resourceUri.toString(), decoration); } } diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 2bf9ec4c47f..8290560e3ce 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -793,7 +793,7 @@ export class GitStatusParser { // space i++; - if (entry.x === 'R' || entry.x === 'C') { + if (entry.x === 'R' || entry.y === 'R' || entry.x === 'C') { lastIndex = raw.indexOf('\0', i); if (lastIndex === -1) { diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index e7ed1cad955..09f0355f9e7 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -58,6 +58,7 @@ export class Resource implements SourceControlResourceState { case Status.UNTRACKED: return l10n.t('Untracked'); case Status.IGNORED: return l10n.t('Ignored'); case Status.INTENT_TO_ADD: return l10n.t('Intent to Add'); + case Status.INTENT_TO_RENAME: return l10n.t('Intent to Rename'); case Status.BOTH_DELETED: return l10n.t('Conflict: Both Deleted'); case Status.ADDED_BY_US: return l10n.t('Conflict: Added By Us'); case Status.DELETED_BY_THEM: return l10n.t('Conflict: Deleted By Them'); @@ -71,7 +72,7 @@ export class Resource implements SourceControlResourceState { @memoize get resourceUri(): Uri { - if (this.renameResourceUri && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED || this._type === Status.INDEX_COPIED)) { + if (this.renameResourceUri && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED || this._type === Status.INDEX_COPIED || this._type === Status.INTENT_TO_RENAME)) { return this.renameResourceUri; } @@ -136,6 +137,7 @@ export class Resource implements SourceControlResourceState { case Status.UNTRACKED: return Resource.Icons[theme].Untracked; case Status.IGNORED: return Resource.Icons[theme].Ignored; case Status.INTENT_TO_ADD: return Resource.Icons[theme].Added; + case Status.INTENT_TO_RENAME: return Resource.Icons[theme].Renamed; case Status.BOTH_DELETED: return Resource.Icons[theme].Conflict; case Status.ADDED_BY_US: return Resource.Icons[theme].Conflict; case Status.DELETED_BY_THEM: return Resource.Icons[theme].Conflict; @@ -193,6 +195,7 @@ export class Resource implements SourceControlResourceState { case Status.DELETED: return 'D'; case Status.INDEX_RENAMED: + case Status.INTENT_TO_RENAME: return 'R'; case Status.UNTRACKED: return 'U'; @@ -230,6 +233,7 @@ export class Resource implements SourceControlResourceState { return new ThemeColor('gitDecoration.addedResourceForeground'); case Status.INDEX_COPIED: case Status.INDEX_RENAMED: + case Status.INTENT_TO_RENAME: return new ThemeColor('gitDecoration.renamedResourceForeground'); case Status.UNTRACKED: return new ThemeColor('gitDecoration.untrackedResourceForeground'); @@ -520,6 +524,7 @@ class ResourceCommandResolver { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: case Status.INDEX_ADDED: + case Status.INTENT_TO_RENAME: return toGitUri(resource.original, 'HEAD'); case Status.MODIFIED: @@ -554,7 +559,8 @@ class ResourceCommandResolver { case Status.MODIFIED: case Status.UNTRACKED: case Status.IGNORED: - case Status.INTENT_TO_ADD: { + case Status.INTENT_TO_ADD: + case Status.INTENT_TO_RENAME: { const uriString = resource.resourceUri.toString(); const [indexStatus] = this.repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); @@ -599,6 +605,10 @@ class ResourceCommandResolver { case Status.UNTRACKED: return l10n.t('{0} (Untracked)', basename); + case Status.INTENT_TO_ADD: + case Status.INTENT_TO_RENAME: + return l10n.t('{0} (Intent to add)', basename); + default: return ''; } @@ -2177,6 +2187,7 @@ export class Repository implements Disposable { case 'M': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri)); break; case 'D': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break; case 'A': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break; + case 'R': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_RENAME, useIcons, renameUri)); break; } return undefined; diff --git a/extensions/github/src/typings/git.d.ts b/extensions/github/src/typings/git.d.ts index 2470ddfbe99..4b4acd66879 100644 --- a/extensions/github/src/typings/git.d.ts +++ b/extensions/github/src/typings/git.d.ts @@ -78,6 +78,7 @@ export const enum Status { UNTRACKED, IGNORED, INTENT_TO_ADD, + INTENT_TO_RENAME, ADDED_BY_US, ADDED_BY_THEM, From ce7986f35edb62bff741768b4840eb2199c82b8b Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 24 May 2023 15:02:56 +0200 Subject: [PATCH 34/65] extract logic into `EditorChatFollowUp` class --- .../src/languageFeatures/quickFix.ts | 78 ++++++++++--------- 1 file changed, 42 insertions(+), 36 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 9d5ba9fdef2..849c2a71356 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -23,9 +23,46 @@ type ApplyCodeActionCommand_args = { readonly document: vscode.TextDocument; readonly diagnostic: vscode.Diagnostic; readonly action: Proto.CodeFixAction; - readonly followupAction: (document: vscode.TextDocument, diagnostic: vscode.Diagnostic, action: Proto.CodeFixAction, client: ITypeScriptServiceClient) => Promise | undefined; + readonly followupAction?: Command; }; +class EditorChatFollowUp implements Command { + + id: string = 'needsBetterName.editorChateFollowUp'; + + constructor(private readonly prompt: string, private readonly document: vscode.TextDocument, private readonly range: vscode.Range, private readonly client: ITypeScriptServiceClient) { + + } + + async execute() { + const findScopeEndLineFromNavTree = (startLine: number, navigationTree: Proto.NavigationTree[]): number => { + for (const node of navigationTree) { + const { start: { line: nodeStartLine }, end: { line: nodeEndLine } } = typeConverters.Range.fromTextSpan(node.spans[0]); + if (startLine === nodeStartLine) { + return nodeEndLine; + } else if (startLine > nodeStartLine && startLine <= nodeEndLine && node.childItems) { + return findScopeEndLineFromNavTree(startLine, node.childItems); + } + } + return -1; + }; + const filepath = this.client.toOpenTsFilePath(this.document); + if (!filepath) { + return; + } + const response = await this.client.execute('navtree', { file: filepath }, (new vscode.CancellationTokenSource()).token); + if (response.type !== 'response' || !response.body?.childItems) { + return; + } + const startLine = this.range.start.line + 1; + const endLine = findScopeEndLineFromNavTree(startLine, response.body.childItems); + if (endLine === -1) { + return; + } + await vscode.commands.executeCommand('interactiveEditor.start', { initialRange: { startLineNumber: startLine, startColumn: 1, endLineNumber: endLine, endColumn: 1 }, message: this.prompt, autoSend: true }); + } +} + class ApplyCodeActionCommand implements Command { public static readonly ID = '_typescript.applyCodeActionCommand'; public readonly id = ApplyCodeActionCommand.ID; @@ -52,9 +89,7 @@ class ApplyCodeActionCommand implements Command { this.diagnosticManager.deleteDiagnostic(document.uri, diagnostic); const codeActionResult = await applyCodeActionCommands(this.client, action.commands, nulToken); - if (followupAction) { - await followupAction(document, diagnostic, action, this.client); - } + await followupAction?.execute(); return codeActionResult; } } @@ -323,43 +358,14 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider { - const findScopeEndLineFromNavTree = (startLine: number, navigationTree: Proto.NavigationTree[]): number => { - for (const node of navigationTree) { - const nodeStartLine = node.spans[0].start.line; - const nodeEndLine = node.spans[0].end.line; - if (startLine === nodeStartLine) { - return nodeEndLine; - } else if (startLine > nodeStartLine && startLine <= nodeEndLine && node.childItems) { - return findScopeEndLineFromNavTree(startLine, node.childItems); - } - } - return -1; - }; - const filepath = client.toOpenTsFilePath(document); - if (!filepath) { - return; - } - const response = await client.execute('navtree', { file: filepath }, (new vscode.CancellationTokenSource()).token); - if (response.type !== 'response' || !response.body?.childItems) { - return; - } - const diagnosticStartLine = diagnostic.range.start.line + 1; - const endLineFound = findScopeEndLineFromNavTree(diagnosticStartLine, response.body.childItems); - const endLine = endLineFound !== -1 ? endLineFound : document.lineCount; - const startLine = endLineFound !== -1 ? diagnosticStartLine : 0; - await vscode.commands.executeCommand('interactiveEditor.start', { initialRange: { startLineNumber: startLine, endLineNumber: endLine, startColumn: 0, endColumn: 0 }, message: 'Implement the class using the interface', autoSend: true }); - } - private getSingleFixForTsCodeAction( document: vscode.TextDocument, diagnostic: vscode.Diagnostic, tsAction: Proto.CodeFixAction ): VsCodeCodeAction { - let followupAction; - switch (tsAction.fixName) { - case 'fixClassIncorrectlyImplementsInterface': - followupAction = this.classIncorrectlyImplementsInterfaceFollowupAction; + let followupAction: Command | undefined; + if (tsAction.fixName === fixNames.classIncorrectlyImplementsInterface) { + followupAction = new EditorChatFollowUp('Implement the class using the interface', document, diagnostic.range, this.client); } const codeAction = new VsCodeCodeAction(tsAction, tsAction.description, vscode.CodeActionKind.QuickFix); codeAction.edit = getEditForCodeAction(this.client, tsAction); From 2b8529fa61d12f27625b5adce1d53f1850c38806 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 24 May 2023 15:21:18 +0200 Subject: [PATCH 35/65] make API command for `interactiveEditor.start` so that initial range can be set properly --- .../src/languageFeatures/quickFix.ts | 20 ++++++------ .../workbench/api/common/extHost.api.impl.ts | 2 +- .../api/common/extHostInteractiveEditor.ts | 32 +++++++++++++++++++ 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 849c2a71356..62cbfb24c8f 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -35,16 +35,16 @@ class EditorChatFollowUp implements Command { } async execute() { - const findScopeEndLineFromNavTree = (startLine: number, navigationTree: Proto.NavigationTree[]): number => { + const findScopeEndLineFromNavTree = (startLine: number, navigationTree: Proto.NavigationTree[]): vscode.Range | undefined => { for (const node of navigationTree) { - const { start: { line: nodeStartLine }, end: { line: nodeEndLine } } = typeConverters.Range.fromTextSpan(node.spans[0]); - if (startLine === nodeStartLine) { - return nodeEndLine; - } else if (startLine > nodeStartLine && startLine <= nodeEndLine && node.childItems) { + const range = typeConverters.Range.fromTextSpan(node.spans[0]); + if (startLine === range.start.line) { + return range; + } else if (startLine > range.start.line && startLine <= range.end.line && node.childItems) { return findScopeEndLineFromNavTree(startLine, node.childItems); } } - return -1; + return undefined; }; const filepath = this.client.toOpenTsFilePath(this.document); if (!filepath) { @@ -54,12 +54,12 @@ class EditorChatFollowUp implements Command { if (response.type !== 'response' || !response.body?.childItems) { return; } - const startLine = this.range.start.line + 1; - const endLine = findScopeEndLineFromNavTree(startLine, response.body.childItems); - if (endLine === -1) { + const startLine = this.range.start.line; + const enclosingRange = findScopeEndLineFromNavTree(startLine, response.body.childItems); + if (!enclosingRange) { return; } - await vscode.commands.executeCommand('interactiveEditor.start', { initialRange: { startLineNumber: startLine, startColumn: 1, endLineNumber: endLine, endColumn: 1 }, message: this.prompt, autoSend: true }); + await vscode.commands.executeCommand('vscode.editorChat.start', { initialRange: enclosingRange, message: this.prompt, autoSend: true }); } } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 3a8b64fa044..7918294f9e2 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -202,7 +202,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol)); const extHostProfileContentHandlers = rpcProtocol.set(ExtHostContext.ExtHostProfileContentHandlers, new ExtHostProfileContentHandlers(rpcProtocol)); rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); - const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInteractiveEditor, new ExtHostInteractiveEditor(rpcProtocol, extHostDocuments, extHostLogService)); + const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInteractiveEditor, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol, extHostLogService)); const extHostSemanticSimilarity = rpcProtocol.set(ExtHostContext.ExtHostSemanticSimilarity, new ExtHostSemanticSimilarity(rpcProtocol)); const extHostIssueReporter = rpcProtocol.set(ExtHostContext.ExtHostIssueReporter, new ExtHostIssueReporter(rpcProtocol)); diff --git a/src/vs/workbench/api/common/extHostInteractiveEditor.ts b/src/vs/workbench/api/common/extHostInteractiveEditor.ts index 94ba77eeb02..90164a44aab 100644 --- a/src/vs/workbench/api/common/extHostInteractiveEditor.ts +++ b/src/vs/workbench/api/common/extHostInteractiveEditor.ts @@ -15,6 +15,8 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import type * as vscode from 'vscode'; +import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IRange } from 'vs/editor/common/core/range'; class ProviderWrapper { @@ -47,10 +49,40 @@ export class ExtHostInteractiveEditor implements ExtHostInteractiveEditorShape { constructor( mainContext: IMainContext, + extHostCommands: ExtHostCommands, private readonly _documents: ExtHostDocuments, private readonly _logService: ILogService, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadInteractiveEditor); + + type EditorChatApiArg = { + initialRange?: vscode.Range; + message?: string; + autoSend?: boolean; + }; + + type InteractiveEditorRunOptions = { + initialRange?: IRange; + message?: string; + autoSend?: boolean; + }; + + extHostCommands.registerApiCommand(new ApiCommand( + 'vscode.editorChat.start', 'interactiveEditor.start', 'Invoke a new editor chat session', + [new ApiCommandArgument('Run arguments', '', _v => true, v => { + + if (!v) { + return undefined; + } + + return { + initialRange: v.initialRange ? typeConvert.Range.from(v.initialRange) : undefined, + message: v.message, + autoSend: v.autoSend + }; + })], + ApiCommandResult.Void + )); } registerProvider(extension: Readonly, provider: vscode.InteractiveEditorSessionProvider): vscode.Disposable { From 51325c051938da4e90665f55b2a252d25cd91dfa Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 24 May 2023 15:50:58 +0200 Subject: [PATCH 36/65] Don't focus out nor into editor chat when accessibility mode is on (#183318) fixes https://github.com/microsoft/vscode-internalbacklog/issues/4166 --- .../interactiveEditor/browser/interactiveEditorActions.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts index 4946a918df2..d76656484e1 100644 --- a/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts +++ b/src/vs/workbench/contrib/interactiveEditor/browser/interactiveEditorActions.ts @@ -25,6 +25,7 @@ import { Range } from 'vs/editor/common/core/range'; import { fromNow } from 'vs/base/common/date'; import { IInteractiveEditorSessionService, Recording } from 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditorSession'; import { runAccessibilityHelpAction } from 'vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp'; +import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; export class StartSessionAction extends EditorAction2 { @@ -157,7 +158,7 @@ export class ArrowOutUpAction extends AbstractInteractiveEditorAction { super({ id: 'interactiveEditor.arrowOutUp', title: localize('arrowUp', 'Cursor Up'), - precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST, EditorContextKeys.isEmbeddedDiffEditor.negate()), + precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_FIRST, EditorContextKeys.isEmbeddedDiffEditor.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), keybinding: { weight: KeybindingWeight.EditorCore, primary: KeyCode.UpArrow @@ -175,7 +176,7 @@ export class ArrowOutDownAction extends AbstractInteractiveEditorAction { super({ id: 'interactiveEditor.arrowOutDown', title: localize('arrowDown', 'Cursor Down'), - precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST, EditorContextKeys.isEmbeddedDiffEditor.negate()), + precondition: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_FOCUSED, CTX_INTERACTIVE_EDITOR_INNER_CURSOR_LAST, EditorContextKeys.isEmbeddedDiffEditor.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), keybinding: { weight: KeybindingWeight.EditorCore, primary: KeyCode.DownArrow @@ -195,7 +196,7 @@ export class FocusInteractiveEditor extends EditorAction2 { id: 'interactiveEditor.focus', title: localize('focus', 'Focus'), category: AbstractInteractiveEditorAction.category, - precondition: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_FOCUSED.negate()), + precondition: ContextKeyExpr.and(EditorContextKeys.editorTextFocus, CTX_INTERACTIVE_EDITOR_VISIBLE, CTX_INTERACTIVE_EDITOR_FOCUSED.negate(), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), keybinding: [{ weight: KeybindingWeight.EditorCore + 10, // win against core_command when: ContextKeyExpr.and(CTX_INTERACTIVE_EDITOR_OUTER_CURSOR_POSITION.isEqualTo('above'), EditorContextKeys.isEmbeddedDiffEditor.negate()), From 203c911ca066ca22c49af5b5924b9922f5287d00 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 24 May 2023 16:18:04 +0200 Subject: [PATCH 37/65] adding a setting in order to control whether the ai assisted setting is enabled or not --- extensions/typescript-language-features/package.json | 6 ++++++ extensions/typescript-language-features/package.nls.json | 1 + .../src/languageFeatures/quickFix.ts | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 0393fbb0ea4..26fd5e64d96 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -143,6 +143,12 @@ "title": "%configuration.typescript%", "order": 20, "properties": { + "typescript.experimental.aiQuickFix": { + "type": "boolean", + "default": false, + "description": "%typescript.experimental.aiQuickFix%", + "scope": "resource" + }, "typescript.tsdk": { "type": "string", "markdownDescription": "%typescript.tsdk.desc%", diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 2d5aa3e7eb7..c46952747fd 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -8,6 +8,7 @@ "configuration.suggest.completeFunctionCalls": "Complete functions with their parameter signature.", "configuration.suggest.includeAutomaticOptionalChainCompletions": "Enable/disable showing completions on potentially undefined values that insert an optional chain call. Requires strict null checks to be enabled.", "configuration.suggest.includeCompletionsForImportStatements": "Enable/disable auto-import-style completions on partially-typed import statements.", + "typescript.experimental.aiQuickFix": "Enable/disable AI-assisted quick fixes.", "typescript.tsdk.desc": "Specifies the folder path to the tsserver and `lib*.d.ts` files under a TypeScript install to use for IntelliSense, for example: `./node_modules/typescript/lib`.\n\n- When specified as a user setting, the TypeScript version from `typescript.tsdk` automatically replaces the built-in TypeScript version.\n- When specified as a workspace setting, `typescript.tsdk` allows you to switch to use that workspace version of TypeScript for IntelliSense with the `TypeScript: Select TypeScript version` command.\n\nSee the [TypeScript documentation](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-newer-typescript-versions) for more detail about managing TypeScript versions.", "typescript.disableAutomaticTypeAcquisition": "Disables [automatic type acquisition](https://code.visualstudio.com/docs/nodejs/working-with-javascript#_typings-and-automatic-type-acquisition). Automatic type acquisition fetches `@types` packages from npm to improve IntelliSense for external libraries.", "typescript.enablePromptUseWorkspaceTsdk": "Enables prompting of users to use the TypeScript version configured in the workspace for Intellisense.", diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 62cbfb24c8f..a6553902cbe 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -363,8 +363,9 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider Date: Wed, 24 May 2023 10:32:57 -0500 Subject: [PATCH 38/65] try something --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 6 +++--- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 2 +- src/vs/workbench/contrib/chat/common/chatViewModel.ts | 2 -- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index d7d2b996efa..17ef9b0d5c7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -425,7 +425,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.codeBlocksByEditorUri.delete(ref.object.textModel.uri))); @@ -436,7 +435,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.codeBlocksByResponseId.delete(element.id))); } @@ -517,7 +515,9 @@ export class ChatListDelegate implements IListVirtualDelegate { } export class ChatAccessibilityProvider implements IListAccessibilityProvider { + constructor(private readonly _renderer: ChatListItemRenderer) { + } getWidgetRole(): AriaRole { return 'list'; } @@ -547,7 +547,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider isRequestVM(e) ? e.message : isResponseVM(e) ? e.response.value : '' }, // TODO setRowLineHeight: false, overrideStyles: { diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index 9a1a861daec..1619d829028 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -79,8 +79,6 @@ export interface IChatResponseViewModel { readonly commandFollowups?: IChatResponseCommandFollowup[]; readonly errorDetails?: IChatResponseErrorDetails; readonly contentUpdateTimings?: IChatLiveUpdateData; - codeBlockCount?: number; - codeBlockIndex?: number; renderData?: IChatResponseRenderData; currentRenderedHeight: number | undefined; setVote(vote: InteractiveSessionVoteDirection): void; From a69ee6a02988f66332fdf362ab710999d3b94819 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 24 May 2023 10:55:09 -0500 Subject: [PATCH 39/65] try to update aria label after it has been rendered --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 17ef9b0d5c7..e005029c425 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -107,6 +107,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer Date: Wed, 24 May 2023 11:20:01 -0500 Subject: [PATCH 40/65] move code where relevant --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 2 +- src/vs/workbench/contrib/chat/common/chatViewModel.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index e005029c425..b572930a09c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -280,7 +280,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.codeBlocksByResponseId.delete(element.id))); } diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index 1619d829028..ba66ad2c6cd 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -258,8 +258,6 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi renderData: IChatResponseRenderData | undefined = undefined; - codeBlockCount: number | undefined; - currentRenderedHeight: number | undefined; private _contentUpdateTimings: IChatLiveUpdateData | undefined = undefined; From 8f00a45523095c58162438d22d9d86dbb7bef7cc Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 24 May 2023 09:38:10 -0700 Subject: [PATCH 41/65] Pass custom options for `input` dialogs (#183160) * Pass custom options for `input` dialogs * Check against `object` --- src/vs/workbench/browser/parts/dialogs/dialogHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index 34f82543106..4b320bba521 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -70,7 +70,7 @@ export class BrowserDialogHandler extends AbstractDialogHandler { const buttons = this.getInputButtons(input); - const { button, checkboxChecked, values } = await this.doShow(input.type ?? 'question', input.message, buttons, input.detail, buttons.length - 1, input?.checkbox, input.inputs); + const { button, checkboxChecked, values } = await this.doShow(input.type ?? 'question', input.message, buttons, input.detail, buttons.length - 1, input?.checkbox, input.inputs, typeof input.custom === 'object' ? input.custom : undefined); return { confirmed: button === 0, checkboxChecked, values }; } From 1753f9697f66ee8c5eec4ba04bec5ffcd630d65a Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 24 May 2023 16:40:11 +0000 Subject: [PATCH 42/65] Update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e3146a1e297..4779e1200e8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.79.0", - "distro": "ba0897486f9570f3f031bd9512490baf0d646762", + "distro": "7272f69cc607298dc986708a73bd978aa7c3af3d", "author": { "name": "Microsoft Corporation" }, From d1d74981bcc54cbb275eef9770156a525dda6c46 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 24 May 2023 09:51:03 -0700 Subject: [PATCH 43/65] Add runCommands to default commands to skip shell Fixes #179724 --- src/vs/workbench/contrib/terminal/common/terminal.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts index e910b297258..9b5149079b4 100644 --- a/src/vs/workbench/contrib/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -735,7 +735,8 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ 'workbench.action.togglePanel', 'workbench.action.quickOpenView', 'workbench.action.toggleMaximizedPanel', - 'notification.acceptPrimaryAction' + 'notification.acceptPrimaryAction', + 'runCommands' ]; export const terminalContributionsDescriptor: IExtensionPointDescriptor = { From 031cf0cb64e311bff690f5def93713662b20fc0e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 24 May 2023 10:03:27 -0700 Subject: [PATCH 44/65] Fix importing of xterm-addon-serialize Fixes #183279 --- src/bootstrap-window.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 1c8e0831975..778f1deb9a6 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -119,6 +119,7 @@ 'xterm-addon-canvas': `${baseNodeModulesPath}/xterm-addon-canvas/lib/xterm-addon-canvas.js`, 'xterm-addon-image': `${baseNodeModulesPath}/xterm-addon-image/lib/xterm-addon-image.js`, 'xterm-addon-search': `${baseNodeModulesPath}/xterm-addon-search/lib/xterm-addon-search.js`, + 'xterm-addon-serialize': `${baseNodeModulesPath}/xterm-addon-serialize/lib/xterm-addon-serialize.js`, 'xterm-addon-unicode11': `${baseNodeModulesPath}/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`, 'xterm-addon-webgl': `${baseNodeModulesPath}/xterm-addon-webgl/lib/xterm-addon-webgl.js`, '@vscode/iconv-lite-umd': `${baseNodeModulesPath}/@vscode/iconv-lite-umd/lib/iconv-lite-umd.js`, From d43a206c553db3e6a328c6f72e8f46846907ad13 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Wed, 24 May 2023 10:10:39 -0700 Subject: [PATCH 45/65] Wait for all installed extensions to be registered before rendering welcome page. (#183273) * Move check for lifecycle phase * Wait for extensionsRegistered --- .../welcomeGettingStarted/browser/gettingStarted.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index c7d1435d648..838ac8e0ed5 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -73,7 +73,6 @@ import { IFeaturedExtensionsService } from 'vs/workbench/contrib/welcomeGettingS import { IFeaturedExtension } from 'vs/base/common/product'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; const SLIDE_TRANSITION_TIME_MS = 250; const configurationKey = 'workbench.startupEditor'; @@ -190,9 +189,7 @@ export class GettingStartedPage extends EditorPane { @IWebviewService private readonly webviewService: IWebviewService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IAccessibilityService private readonly accessibilityService: IAccessibilityService, - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, - ) { + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService) { super(GettingStartedPage.ID, telemetryService, themeService, storageService); @@ -305,7 +302,6 @@ export class GettingStartedPage extends EditorPane { this.container.classList.remove('animatable'); this.editorInput = newInput; await super.setInput(newInput, options, context, token); - await this.lifecycleService.when(LifecyclePhase.Restored); await this.buildCategoriesSlide(); if (this.shouldAnimate()) { setTimeout(() => this.container.classList.add('animatable'), 0); @@ -729,6 +725,9 @@ export class GettingStartedPage extends EditorPane { private async buildCategoriesSlide() { + // Delay fetching welcome page content on startup until all extensions are ready. + await this.extensionService.whenInstalledExtensionsRegistered(); + this.recentlyOpened = await this.workspacesService.getRecentlyOpened(); this.gettingStartedCategories = await this.gettingStartedService.getWalkthroughs(); this.featuredExtensions = await this.featuredExtensionService.getExtensions(); From 800142c7dd47b5cf6047ef2c8f4c8440c6e244f6 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 24 May 2023 10:40:10 -0700 Subject: [PATCH 46/65] chore: remove unused experiments url override (#183266) --- .../workbench/contrib/experiments/common/experimentService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index 297b08a29af..24a46094bf9 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -233,7 +233,7 @@ export class ExperimentService extends Disposable implements IExperimentService return []; // TODO@sbatten add CLI argument (https://github.com/microsoft/vscode-internalbacklog/issues/2855) } - const experimentsUrl = this.configurationService.getValue('_workbench.experimentsUrl') || this.productService.experimentsUrl; + const experimentsUrl = this.productService.experimentsUrl; if (!experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) { return []; } From a54803afc9476aff9ada1bb7a44fc05d2edabd34 Mon Sep 17 00:00:00 2001 From: aamunger Date: Wed, 24 May 2023 11:10:31 -0700 Subject: [PATCH 47/65] re-add events each time the output ID is rendered --- extensions/notebook-renderers/src/index.ts | 51 ++++++++++++---------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index 0351509f922..0f095e20a2b 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -277,43 +277,50 @@ function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error: outputElement.classList.add('output-stream'); const text = outputInfo.text(); - const content = createOutputContent(outputInfo.id, [text], ctx.settings.lineLimit, outputScrolling, false); - content.setAttribute('output-item-id', outputInfo.id); + const newContent = createOutputContent(outputInfo.id, [text], ctx.settings.lineLimit, outputScrolling, false); + newContent.setAttribute('output-item-id', outputInfo.id); if (error) { - content.classList.add('error'); + newContent.classList.add('error'); } const scrollTop = outputScrolling ? findScrolledHeight(outputElement) : undefined; + const existingContent = outputElement.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null; + const previousOutputParent = getPreviousMatchingContentGroup(outputElement); + // If the previous output item for the same cell was also a stream, append this output to the previous - const existingContentParent = getPreviousMatchingContentGroup(outputElement) || outputElement.querySelector('div'); - if (existingContentParent) { - const existing = existingContentParent.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null; - if (existing) { - existing.replaceWith(content); - while (content.nextSibling) { + if (previousOutputParent) { + if (existingContent) { + existingContent.replaceWith(newContent); + + } else { + previousOutputParent.appendChild(newContent); + } + previousOutputParent.classList.toggle('scrollbar-visible', previousOutputParent.scrollHeight > previousOutputParent.clientHeight); + previousOutputParent.scrollTop = scrollTop !== undefined ? scrollTop : previousOutputParent.scrollHeight; + } else { + let contentParent = existingContent?.parentElement; + if (existingContent && contentParent) { + existingContent.replaceWith(newContent); + while (newContent.nextSibling) { // clear out any stale content if we had previously combined streaming outputs into this one - content.nextSibling.remove(); + newContent.nextSibling.remove(); } } else { - existingContentParent.appendChild(content); + contentParent = document.createElement('div'); + contentParent.appendChild(newContent); + while (outputElement.firstChild) { + outputElement.removeChild(outputElement.firstChild); + } + outputElement.appendChild(contentParent); } - existingContentParent.classList.toggle('scrollbar-visible', existingContentParent.scrollHeight > existingContentParent.clientHeight); - existingContentParent.scrollTop = scrollTop !== undefined ? scrollTop : existingContentParent.scrollHeight; - } else { - const contentParent = document.createElement('div'); - contentParent.appendChild(content); + contentParent.classList.toggle('scrollable', outputScrolling); contentParent.classList.toggle('word-wrap', ctx.settings.outputWordWrap); disposableStore.push(ctx.onDidChangeSettings(e => { - contentParent.classList.toggle('word-wrap', e.outputWordWrap); + contentParent!.classList.toggle('word-wrap', e.outputWordWrap); })); - - while (outputElement.firstChild) { - outputElement.removeChild(outputElement.firstChild); - } - outputElement.appendChild(contentParent); initializeScroll(contentParent, disposableStore, scrollTop); } From fe761a5ba78c397d2aecbed23fbbeb7d8657bee2 Mon Sep 17 00:00:00 2001 From: First Date: Wed, 24 May 2023 11:33:04 -0700 Subject: [PATCH 48/65] Only show '(workspace)' suffix if there is already a description for the extension --- .../contrib/terminal/browser/environmentVariableInfo.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts b/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts index 0b9638e8e6b..e355b287f63 100644 --- a/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts +++ b/src/vs/workbench/contrib/terminal/browser/environmentVariableInfo.ts @@ -105,7 +105,9 @@ function getMergedDescription(collection: IMergedEnvironmentVariableCollection, } const workspaceDescription = workspaceDescriptions.get(ext); if (workspaceDescription) { - message.push(`\n- \`${getExtensionName(ext, extensionService)} (${localize('ScopedEnvironmentContributionInfo', 'workspace')})\``); + // Only show '(workspace)' suffix if there is already a description for the extension. + const workspaceSuffix = globalDescription ? ` (${localize('ScopedEnvironmentContributionInfo', 'workspace')})` : ''; + message.push(`\n- \`${getExtensionName(ext, extensionService)}${workspaceSuffix}\``); message.push(`: ${workspaceDescription}`); } if (!globalDescription && !workspaceDescription) { From aa1508251b560692aa7566ab55d449bb35188a74 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 24 May 2023 14:02:02 -0500 Subject: [PATCH 49/65] use lexer, revert some hacks --- .../contrib/chat/browser/chatListRenderer.ts | 13 ++++++------- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index b572930a09c..40661cf60ed 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -60,6 +60,7 @@ import { IChatRequestViewModel, IChatResponseViewModel, IChatWelcomeMessageViewM import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; +import { marked } from 'vs/base/common/marked/marked'; const $ = dom.$; @@ -107,7 +108,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this.codeBlocksByResponseId.delete(element.id))); } @@ -518,9 +516,6 @@ export class ChatListDelegate implements IListVirtualDelegate { } export class ChatAccessibilityProvider implements IListAccessibilityProvider { - constructor(private readonly _renderer: ChatListItemRenderer) { - - } getWidgetRole(): AriaRole { return 'list'; } @@ -550,7 +545,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider token.type === 'code')?.length ?? 0; +} + interface IChatResultCodeBlockData { text: string; languageId: string; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 8bd41c459f0..c851bff9e91 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -266,7 +266,7 @@ export class ChatWidget extends Disposable implements IChatWidget { horizontalScrolling: false, supportDynamicHeights: true, hideTwistiesOfChildlessElements: true, - accessibilityProvider: new ChatAccessibilityProvider(this.renderer), + accessibilityProvider: new ChatAccessibilityProvider(), keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: ChatTreeItem) => isRequestVM(e) ? e.message : isResponseVM(e) ? e.response.value : '' }, // TODO setRowLineHeight: false, overrideStyles: { From c758971c32d13fa29aee22e89fc6c935f13782a0 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 24 May 2023 14:03:57 -0500 Subject: [PATCH 50/65] inline --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 40661cf60ed..6f09622c550 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -545,7 +545,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider token.type === 'code')?.length ?? 0; switch (codeBlockCount) { case 0: return element.response.value; @@ -555,10 +555,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider token.type === 'code')?.length ?? 0; } interface IChatResultCodeBlockData { From 2e8789247f8dd8ec8cfb7feafef3eb5f51e2edf8 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 24 May 2023 14:41:31 -0500 Subject: [PATCH 51/65] rm line --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 6f09622c550..89d03609f04 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -555,7 +555,6 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider Date: Wed, 24 May 2023 14:19:24 -0700 Subject: [PATCH 52/65] Pick up latest TS for building VS Code (#183358) --- .../src/languageFeatures/completions.ts | 1 - package.json | 2 +- .../services/textfile/browser/textFileService.ts | 2 +- yarn.lock | 8 ++++---- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/extensions/typescript-language-features/src/languageFeatures/completions.ts b/extensions/typescript-language-features/src/languageFeatures/completions.ts index fcc5dc64c6f..8b5100f876e 100644 --- a/extensions/typescript-language-features/src/languageFeatures/completions.ts +++ b/extensions/typescript-language-features/src/languageFeatures/completions.ts @@ -93,7 +93,6 @@ class MyCompletionItem extends vscode.CompletionItem { this.range = this.getRangeFromReplacementSpan(tsEntry, completionContext); this.commitCharacters = MyCompletionItem.getCommitCharacters(completionContext, tsEntry); this.insertText = isSnippet && tsEntry.insertText ? new vscode.SnippetString(tsEntry.insertText) : tsEntry.insertText; - // @ts-expect-error until 5.2 this.filterText = tsEntry.filterText || this.getFilterText(completionContext.line, tsEntry.insertText); if (completionContext.isMemberCompletion && completionContext.dotAccessorContext && !(this.insertText instanceof vscode.SnippetString)) { diff --git a/package.json b/package.json index 4779e1200e8..0a6957a0fa6 100644 --- a/package.json +++ b/package.json @@ -204,7 +204,7 @@ "ts-loader": "^9.4.2", "ts-node": "^10.9.1", "tsec": "0.1.4", - "typescript": "^5.2.0-dev.20230516", + "typescript": "^5.2.0-dev.20230524", "typescript-formatter": "7.1.0", "underscore": "^1.12.1", "util": "^0.12.4", diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 6920a263671..bebdacc22ba 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -507,7 +507,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex let sourceTextModel: ITextModel | undefined = undefined; if (sourceModel instanceof BaseTextEditorModel) { if (sourceModel.isResolved()) { - sourceTextModel = sourceModel.textEditorModel; + sourceTextModel = withNullAsUndefined(sourceModel.textEditorModel); } } else { sourceTextModel = sourceModel as ITextModel; diff --git a/yarn.lock b/yarn.lock index ed38da917db..b4adddd0a59 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9782,10 +9782,10 @@ typescript@^4.7.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== -typescript@^5.2.0-dev.20230516: - version "5.2.0-dev.20230516" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.0-dev.20230516.tgz#9dc4d02c031cdf311ddb6ee646be09d5f13de4c9" - integrity sha512-DGK8md4PQgA6QG9JnvC6LecNnBstc1h6zrg71isrlZTsRFVl3EsID6D2Exrh4ULbxynA61PE13M+uOmLFWOu4w== +typescript@^5.2.0-dev.20230524: + version "5.2.0-dev.20230524" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.0-dev.20230524.tgz#c2714b82f5ef8c63328a253692ae0b8c244c86d6" + integrity sha512-1XzSUJCt31jm7jIZ3vBKzK46ZxnmqX2VdVg/dur9AIaz9WmidrABs7F8H8d4onpIV8RYD/L6xW6MXR5EHjl+LA== typical@^4.0.0: version "4.0.0" From c71766d5053ddbe6c2bb442ca2433a59723c05de Mon Sep 17 00:00:00 2001 From: aamunger Date: Wed, 24 May 2023 14:24:30 -0700 Subject: [PATCH 53/65] event testing --- .../src/test/notebookRenderer.test.ts | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts index f01aa630956..340fff17c8a 100644 --- a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts +++ b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { activate } from '..'; import { OutputItem, RendererApi } from 'vscode-notebook-renderer'; -import { IRichRenderContext, RenderOptions } from '../rendererTypes'; +import { IDisposable, IRichRenderContext, RenderOptions } from '../rendererTypes'; import { JSDOM } from "jsdom"; const dom = new JSDOM(); @@ -37,7 +37,15 @@ suite('Notebook builtin output renderer', () => { type optionalRenderOptions = { [k in keyof RenderOptions]?: RenderOptions[k] }; + type handler = (e: RenderOptions) => any; + + const settingsChangedHandlers: handler[] = []; + function fireSettingsChange(options: optionalRenderOptions) { + settingsChangedHandlers.forEach((handler) => handler(options as RenderOptions)); + } + function createContext(settings?: optionalRenderOptions): IRichRenderContext { + settingsChangedHandlers.length = 0; return { setState(_value: void) { }, getState() { return undefined; }, @@ -48,9 +56,16 @@ suite('Notebook builtin output renderer', () => { lineLimit: 30, ...settings } as RenderOptions, - onDidChangeSettings(_listener: (e: T) => any, _thisArgs?: any, _disposables?: any) { + onDidChangeSettings(listener: handler, _thisArgs?: any, disposables?: IDisposable[]) { + settingsChangedHandlers.push(listener); + + const dispose = () => { + settingsChangedHandlers.splice(settingsChangedHandlers.indexOf(listener), 1); + }; + + disposables?.push({ dispose }); return { - dispose(): void { } + dispose }; }, workspace: { @@ -250,5 +265,33 @@ suite('Notebook builtin output renderer', () => { assert.ok(inserted.innerHTML.indexOf('>second stream contentthird stream content { + const context = createContext({ outputWordWrap: false, outputScrolling: true }); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputElement = new OutputHtml().getFirstOuputElement(); + const outputItem = createOutputItem('content', stdoutMimeType); + await renderer!.renderOutputItem(outputItem, outputElement); + fireSettingsChange({ outputWordWrap: true, outputScrolling: true }); + + const inserted = outputElement.firstChild as HTMLElement; + assert.ok(inserted.classList.contains('word-wrap') && inserted.classList.contains('scrollable'), + `output content classList should contain word-wrap and scrollable ${inserted.classList}`); + }); + + test(`Settings event change listeners should not grow if output is re-rendered`, async () => { + const context = createContext({ outputWordWrap: false }); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputElement = new OutputHtml().getFirstOuputElement(); + await renderer!.renderOutputItem(createOutputItem('content', stdoutMimeType), outputElement); + const handlerCount = settingsChangedHandlers.length; + await renderer!.renderOutputItem(createOutputItem('content', stdoutMimeType), outputElement); + + assert.equal(settingsChangedHandlers.length, handlerCount); + }); }); From 6675ef9017b1da6edd3e37e69e7c6c2b3b2b7b76 Mon Sep 17 00:00:00 2001 From: Anuj Parajuli <12287017+iAnujParajuli@users.noreply.github.com> Date: Thu, 25 May 2023 03:13:52 +0545 Subject: [PATCH 54/65] Adds #181652 html audio tag for audio file (#183328) --- .../src/languageFeatures/copyFiles/shared.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts index 3892941b7ce..0b108e0b9cf 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts @@ -14,6 +14,7 @@ import { getDocumentDir } from '../../util/document'; enum MediaKind { Image, Video, + Audio, } export const mediaFileExtensions = new Map([ @@ -35,6 +36,11 @@ export const mediaFileExtensions = new Map([ // Videos ['ogg', MediaKind.Video], ['mp4', MediaKind.Video], + + // Audio Files + ['mp3', MediaKind.Audio], + ['aac', MediaKind.Audio], + ['wav', MediaKind.Audio], ]); export const mediaMimes = new Set([ @@ -45,6 +51,9 @@ export const mediaMimes = new Set([ 'image/webp', 'video/mp4', 'video/ogg', + 'audio/mpeg', + 'audio/aac', + 'audio/x-wav', ]); @@ -104,12 +113,18 @@ export function createUriListSnippet( const ext = URI.Utils.extname(uri).toLowerCase().replace('.', ''); const insertAsMedia = typeof options?.insertAsMedia === 'undefined' ? mediaFileExtensions.has(ext) : !!options.insertAsMedia; const insertAsVideo = mediaFileExtensions.get(ext) === MediaKind.Video; + const insertAsAudio = mediaFileExtensions.get(ext) === MediaKind.Audio; if (insertAsVideo) { insertedImageCount++; snippet.appendText(`'); + } else if (insertAsAudio) { + insertedImageCount++; + snippet.appendText(`'); } else { if (insertAsMedia) { insertedImageCount++; From 80d9edb7bcd8a521bf25102466a3f69c753c0b1e Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 24 May 2023 15:44:55 -0700 Subject: [PATCH 55/65] Render share link text as readonly div (#183372) --- .../share/browser/share.contribution.ts | 22 ++++++++++++------- .../workbench/contrib/share/browser/share.css | 18 +++++++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 src/vs/workbench/contrib/share/browser/share.css diff --git a/src/vs/workbench/contrib/share/browser/share.contribution.ts b/src/vs/workbench/contrib/share/browser/share.contribution.ts index 7b8386eb3ed..a1819051c93 100644 --- a/src/vs/workbench/contrib/share/browser/share.contribution.ts +++ b/src/vs/workbench/contrib/share/browser/share.contribution.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./share'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Codicon } from 'vs/base/common/codicons'; +import { MarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { localize } from 'vs/nls'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -78,19 +80,23 @@ class ShareWorkbenchContribution { const uri = await shareService.provideShare({ resourceUri }, new CancellationTokenSource().token); if (uri) { - await clipboardService.writeText(uri.toString()); - const result = await dialogService.input( + const uriText = uri.toString(); + await clipboardService.writeText(uriText); + dialogService.prompt( { type: Severity.Info, - inputs: [{ type: 'text', value: uri.toString() }], message: localize('shareSuccess', 'Copied link to clipboard!'), - custom: { icon: Codicon.check }, - primaryButton: localize('open link', 'Open Link') + custom: { + icon: Codicon.check, + markdownDetails: [{ + markdown: new MarkdownString(`
${uriText}
`, { supportHtml: true }), + classes: ['share-dialog-input'] + }] + }, + cancelButton: localize('close', 'Close'), + buttons: [{ label: localize('open link', 'Open Link'), run: () => { urlService.open(uri, { openExternal: true }); } }] } ); - if (result.confirmed) { - urlService.open(uri, { openExternal: true }); - } } } }); diff --git a/src/vs/workbench/contrib/share/browser/share.css b/src/vs/workbench/contrib/share/browser/share.css new file mode 100644 index 00000000000..5d9469d49c6 --- /dev/null +++ b/src/vs/workbench/contrib/share/browser/share.css @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +div.share-dialog-input { + border: 1px solid var(--vscode-input-border, transparent); + border-radius: 2px; + color: var(--vscode-input-foreground); + background-color: var(--vscode-input-background); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + padding: 2px; + user-select: all; + line-height: 24px; +} + From 3c161fd2b1c31757f571c7f7ff02b57a5297eb14 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 24 May 2023 16:30:06 -0700 Subject: [PATCH 56/65] Add better label when adding video/video to md (#183379) --- .../src/languageFeatures/copyFiles/shared.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts index 0b108e0b9cf..c0ecbe9a850 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts @@ -106,6 +106,7 @@ export function createUriListSnippet( let insertedLinkCount = 0; let insertedImageCount = 0; + let insertedAudioVideoCount = 0; uris.forEach((uri, i) => { const mdPath = getMdPath(dir, uri); @@ -116,12 +117,12 @@ export function createUriListSnippet( const insertAsAudio = mediaFileExtensions.get(ext) === MediaKind.Audio; if (insertAsVideo) { - insertedImageCount++; + insertedAudioVideoCount++; snippet.appendText(`'); } else if (insertAsAudio) { - insertedImageCount++; + insertedAudioVideoCount++; snippet.appendText(`'); @@ -147,7 +148,13 @@ export function createUriListSnippet( }); let label: string; - if (insertedImageCount > 0 && insertedLinkCount > 0) { + if (insertedAudioVideoCount > 0) { + if (insertedLinkCount > 0) { + label = vscode.l10n.t('Insert Markdown Media and Links'); + } else { + label = vscode.l10n.t('Insert Markdown Media'); + } + } else if (insertedImageCount > 0 && insertedLinkCount > 0) { label = vscode.l10n.t('Insert Markdown Images and Links'); } else if (insertedImageCount > 0) { label = insertedImageCount > 1 From 1fc4a6b900a8e1ddd27c4c0c5a759aa78a7da73d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 25 May 2023 01:37:04 +0200 Subject: [PATCH 57/65] support hosted web extension (#183375) --- src/vs/workbench/browser/web.api.ts | 10 +++- .../browser/webExtensionsScannerService.ts | 46 ++++++++++++++++--- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/browser/web.api.ts b/src/vs/workbench/browser/web.api.ts index 0a7d082cea1..2bd0fd4aefb 100644 --- a/src/vs/workbench/browser/web.api.ts +++ b/src/vs/workbench/browser/web.api.ts @@ -220,7 +220,7 @@ export interface IWorkbenchConstructionOptions { * - an extension in the Marketplace * - location of the extension where it is hosted. */ - readonly additionalBuiltinExtensions?: readonly (MarketplaceExtension | UriComponents)[]; + readonly additionalBuiltinExtensions?: readonly (MarketplaceExtension | UriComponents | HostedExtension)[]; /** * List of extensions to be enabled if they are installed. @@ -373,6 +373,14 @@ export interface IResourceUriProvider { export type ExtensionId = string; export type MarketplaceExtension = ExtensionId | { readonly id: ExtensionId; preRelease?: boolean; migrateStorageFrom?: ExtensionId }; +export interface HostedExtension { + readonly location: UriComponents; + readonly preRelease?: boolean; + readonly packageNLSUris?: Map; + readonly fallbackPackageNLSUri?: UriComponents; + readonly readmeUri?: UriComponents; + readonly changelogUri?: UriComponents; +} export interface ICommonTelemetryPropertiesResolver { (): { [key: string]: any }; diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index e83df848d93..23c82da1c84 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -45,6 +45,14 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; type GalleryExtensionInfo = { readonly id: string; preRelease?: boolean; migrateStorageFrom?: string }; +interface HostedExtensionInfo { + readonly location: UriComponents; + readonly preRelease?: boolean; + readonly packageNLSUris?: Map; + readonly fallbackPackageNLSUri?: UriComponents; + readonly readmeUri?: UriComponents; + readonly changelogUri?: UriComponents; +} type ExtensionInfo = { readonly id: string; preRelease: boolean }; function isGalleryExtensionInfo(obj: unknown): obj is GalleryExtensionInfo { @@ -54,6 +62,23 @@ function isGalleryExtensionInfo(obj: unknown): obj is GalleryExtensionInfo { && (galleryExtensionInfo.migrateStorageFrom === undefined || typeof galleryExtensionInfo.migrateStorageFrom === 'string'); } +function isHostedExtensionInfo(obj: unknown): obj is HostedExtensionInfo { + const hostedExtensionInfo = obj as HostedExtensionInfo | undefined; + return isUriComponents(hostedExtensionInfo?.location) + && (hostedExtensionInfo?.preRelease === undefined || typeof hostedExtensionInfo.preRelease === 'boolean') + && (hostedExtensionInfo?.fallbackPackageNLSUri === undefined || isUriComponents(hostedExtensionInfo?.fallbackPackageNLSUri)) + && (hostedExtensionInfo?.changelogUri === undefined || isUriComponents(hostedExtensionInfo?.changelogUri)) + && (hostedExtensionInfo?.readmeUri === undefined || isUriComponents(hostedExtensionInfo?.readmeUri)); +} + +function isUriComponents(thing: unknown): thing is UriComponents { + if (!thing) { + return false; + } + return isString((thing).path) && + isString((thing).scheme); +} + interface IStoredWebExtension { readonly identifier: IExtensionIdentifier; readonly version: string; @@ -117,12 +142,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } } - private _customBuiltinExtensionsInfoPromise: Promise<{ extensions: ExtensionInfo[]; extensionsToMigrate: [string, string][]; extensionLocations: URI[] }> | undefined; - private readCustomBuiltinExtensionsInfoFromEnv(): Promise<{ extensions: ExtensionInfo[]; extensionsToMigrate: [string, string][]; extensionLocations: URI[] }> { + private _customBuiltinExtensionsInfoPromise: Promise<{ extensions: ExtensionInfo[]; extensionsToMigrate: [string, string][]; extensionLocations: HostedExtensionInfo[] }> | undefined; + private readCustomBuiltinExtensionsInfoFromEnv(): Promise<{ extensions: ExtensionInfo[]; extensionsToMigrate: [string, string][]; extensionLocations: HostedExtensionInfo[] }> { if (!this._customBuiltinExtensionsInfoPromise) { this._customBuiltinExtensionsInfoPromise = (async () => { let extensions: ExtensionInfo[] = []; - const extensionLocations: URI[] = []; + const extensionLocations: HostedExtensionInfo[] = []; const extensionsToMigrate: [string, string][] = []; const customBuiltinExtensionsInfo = this.environmentService.options && Array.isArray(this.environmentService.options.additionalBuiltinExtensions) ? this.environmentService.options.additionalBuiltinExtensions.map(additionalBuiltinExtension => isString(additionalBuiltinExtension) ? { id: additionalBuiltinExtension } : additionalBuiltinExtension) @@ -134,7 +159,11 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten extensionsToMigrate.push([e.migrateStorageFrom, e.id]); } } else { - extensionLocations.push(URI.revive(e)); + if (isHostedExtensionInfo(e)) { + extensionLocations.push(e); + } else { + extensionLocations.push({ location: e }); + } } } if (extensions.length) { @@ -212,9 +241,14 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten return []; } const result: IScannedExtension[] = []; - await Promise.allSettled(extensionLocations.map(async location => { + await Promise.allSettled(extensionLocations.map(async ({ location, preRelease, packageNLSUris, fallbackPackageNLSUri, readmeUri, changelogUri }) => { try { - const webExtension = await this.toWebExtension(location); + const webExtension = await this.toWebExtension(URI.revive(location), undefined, + packageNLSUris ? [...packageNLSUris.entries()].reduce((result, [key, value]) => { result.set(key, URI.revive(value)); return result; }, new Map()) : undefined, + URI.revive(fallbackPackageNLSUri), + URI.revive(readmeUri), + URI.revive(changelogUri), + { preRelease }); const extension = await this.toScannedExtension(webExtension, true); if (extension.isValid || !scanOptions?.skipInvalidExtensions) { result.push(extension); From d5643344e1d022d11cbe33a9ed7b4ea05bc8d2b8 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 24 May 2023 16:37:56 -0700 Subject: [PATCH 58/65] Sort and filter share providers (#183377) --- src/vs/workbench/api/browser/mainThreadShare.ts | 3 ++- src/vs/workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostShare.ts | 2 +- src/vs/workbench/contrib/share/browser/shareService.ts | 5 ++++- src/vs/workbench/contrib/share/common/share.ts | 1 + 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadShare.ts b/src/vs/workbench/api/browser/mainThreadShare.ts index 60f86f96ed0..c1ab121b007 100644 --- a/src/vs/workbench/api/browser/mainThreadShare.ts +++ b/src/vs/workbench/api/browser/mainThreadShare.ts @@ -24,11 +24,12 @@ export class MainThreadShare implements MainThreadShareShape { this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostShare); } - $registerShareProvider(handle: number, selector: IDocumentFilterDto[], id: string, label: string): void { + $registerShareProvider(handle: number, selector: IDocumentFilterDto[], id: string, label: string, priority: number): void { const provider: IShareProvider = { id, label, selector, + priority, provideShare: async (item: IShareableItem) => { return URI.revive(await this.proxy.$provideShare(handle, item, new CancellationTokenSource().token)); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 438fa03cd44..785e83b8b8a 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1265,7 +1265,7 @@ export interface MainThreadSearchShape extends IDisposable { } export interface MainThreadShareShape extends IDisposable { - $registerShareProvider(handle: number, selector: IDocumentFilterDto[], id: string, label: string): void; + $registerShareProvider(handle: number, selector: IDocumentFilterDto[], id: string, label: string, priority: number): void; $unregisterShareProvider(handle: number): void; } diff --git a/src/vs/workbench/api/common/extHostShare.ts b/src/vs/workbench/api/common/extHostShare.ts index 15acba45c47..618fe17138f 100644 --- a/src/vs/workbench/api/common/extHostShare.ts +++ b/src/vs/workbench/api/common/extHostShare.ts @@ -32,7 +32,7 @@ export class ExtHostShare implements ExtHostShareShape { registerShareProvider(selector: vscode.DocumentSelector, provider: vscode.ShareProvider): vscode.Disposable { const handle = ExtHostShare.handlePool++; this.providers.set(handle, provider); - this.proxy.$registerShareProvider(handle, DocumentSelector.from(selector, this.uriTransformer), provider.id, provider.label); + this.proxy.$registerShareProvider(handle, DocumentSelector.from(selector, this.uriTransformer), provider.id, provider.label, provider.priority); return { dispose: () => { this.proxy.$unregisterShareProvider(handle); diff --git a/src/vs/workbench/contrib/share/browser/shareService.ts b/src/vs/workbench/contrib/share/browser/shareService.ts index 8e471a1593f..6fe98f2a5b0 100644 --- a/src/vs/workbench/contrib/share/browser/shareService.ts +++ b/src/vs/workbench/contrib/share/browser/shareService.ts @@ -6,6 +6,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { score } from 'vs/editor/common/languageSelector'; import { localize } from 'vs/nls'; import { ISubmenuItem } from 'vs/platform/actions/common/actions'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -46,7 +47,9 @@ export class ShareService implements IShareService { } async provideShare(item: IShareableItem, token: CancellationToken): Promise { - const providers = [...this._providers.values()]; + const providers = [...this._providers.values()] + .filter((p) => score(p.selector, item.resourceUri, '', true, undefined, undefined) > 0) + .sort((a, b) => a.priority - b.priority); if (providers.length === 0) { return undefined; diff --git a/src/vs/workbench/contrib/share/common/share.ts b/src/vs/workbench/contrib/share/common/share.ts index dcde5d780ad..b389d71c38b 100644 --- a/src/vs/workbench/contrib/share/common/share.ts +++ b/src/vs/workbench/contrib/share/common/share.ts @@ -19,6 +19,7 @@ export interface IShareableItem { export interface IShareProvider { readonly id: string; readonly label: string; + readonly priority: number; readonly selector: LanguageSelector; prepareShare?(item: IShareableItem, token: CancellationToken): Thenable; provideShare(item: IShareableItem, token: CancellationToken): Thenable; From 1c6825fe89036e6b20c88c9f7fc140529092312b Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 24 May 2023 17:25:40 -0700 Subject: [PATCH 59/65] Implement "remove request/response" for Chat (#183380) --- src/vs/platform/actions/common/actions.ts | 2 +- .../workbench/api/browser/mainThreadChat.ts | 7 ++- .../workbench/api/common/extHost.protocol.ts | 1 + src/vs/workbench/api/common/extHostChat.ts | 23 +++++++- .../chat/browser/actions/chatTitleActions.ts | 53 ++++++++++++++++--- .../contrib/chat/browser/chatListRenderer.ts | 8 +-- .../contrib/chat/browser/media/chat.css | 4 +- .../contrib/chat/common/chatContextKeys.ts | 3 ++ .../contrib/chat/common/chatModel.ts | 45 +++++++++++++--- .../contrib/chat/common/chatService.ts | 4 +- .../contrib/chat/common/chatServiceImpl.ts | 18 ++++++- .../contrib/chat/common/chatViewModel.ts | 33 +++++++++++- .../vscode.proposed.interactive.d.ts | 3 ++ 13 files changed, 175 insertions(+), 29 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 18e0c99f060..df156d0bbc7 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -183,7 +183,7 @@ export class MenuId { static readonly InlineSuggestionToolbar = new MenuId('InlineSuggestionToolbar'); static readonly ChatContext = new MenuId('ChatContext'); static readonly ChatCodeBlock = new MenuId('ChatCodeblock'); - static readonly ChatTitle = new MenuId('ChatTitle'); + static readonly ChatMessageTitle = new MenuId('ChatMessageTitle'); static readonly ChatExecute = new MenuId('ChatExecute'); /** diff --git a/src/vs/workbench/api/browser/mainThreadChat.ts b/src/vs/workbench/api/browser/mainThreadChat.ts index fc6e18b077d..807882e0867 100644 --- a/src/vs/workbench/api/browser/mainThreadChat.ts +++ b/src/vs/workbench/api/browser/mainThreadChat.ts @@ -8,10 +8,10 @@ import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; -import { ExtHostContext, ExtHostChatShape, IChatRequestDto, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatShape, ExtHostContext, IChatRequestDto, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; -import { IChatProgress, IChatRequest, IChatResponse, IChat, IChatDynamicRequest, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatDynamicRequest, IChatProgress, IChatRequest, IChatResponse, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadChat) @@ -134,6 +134,9 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { }, provideFollowups: (session, token) => { return this._proxy.$provideFollowups(handle, session.id, token); + }, + removeRequest: (session, requestId) => { + return this._proxy.$removeRequest(handle, session.id, requestId); } }); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 785e83b8b8a..ec87e2d7c83 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1174,6 +1174,7 @@ export interface ExtHostChatShape { $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IChatReplyFollowup[])[] | undefined>; $provideFollowups(handle: number, sessionId: number, token: CancellationToken): Promise; $provideReply(handle: number, sessionId: number, request: IChatRequestDto, token: CancellationToken): Promise; + $removeRequest(handle: number, sessionId: number, requestId: string): void; $provideSlashCommands(handle: number, sessionId: number, token: CancellationToken): Promise; $releaseSession(sessionId: number): void; $onDidPerformUserAction(event: IChatUserActionEvent): Promise; diff --git a/src/vs/workbench/api/common/extHostChat.ts b/src/vs/workbench/api/common/extHostChat.ts index b4aada3a1a0..2a623150731 100644 --- a/src/vs/workbench/api/common/extHostChat.ts +++ b/src/vs/workbench/api/common/extHostChat.ts @@ -13,7 +13,7 @@ import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/exte import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatShape, IChatRequestDto, IChatResponseDto, IChatDto, IMainContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { IChatFollowup, IChatReplyFollowup, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; import type * as vscode from 'vscode'; class ChatProviderWrapper { @@ -158,6 +158,24 @@ export class ExtHostChat implements ExtHostChatShape { return rawFollowups?.map(f => typeConvert.ChatFollowup.from(f)); } + $removeRequest(handle: number, sessionId: number, requestId: string): void { + const entry = this._chatProvider.get(handle); + if (!entry) { + return; + } + + const realSession = this._chatSessions.get(sessionId); + if (!realSession) { + return; + } + + if (!entry.provider.removeRequest) { + return; + } + + entry.provider.removeRequest(realSession, requestId); + } + async $provideReply(handle: number, sessionId: number, request: IChatRequestDto, token: CancellationToken): Promise { const entry = this._chatProvider.get(handle); if (!entry) { @@ -186,7 +204,8 @@ export class ExtHostChat implements ExtHostChatShape { firstProgress = stopWatch.elapsed(); } - this._proxy.$acceptResponseProgress(handle, sessionId, progress); + const vscodeProgress: IChatProgress = 'responseId' in progress ? { requestId: progress.responseId } : progress; + this._proxy.$acceptResponseProgress(handle, sessionId, vscodeProgress); } }; let result: vscode.InteractiveResponseForProgress | undefined | null; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index 40b6a5dc088..222d89dc682 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -9,11 +9,12 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; -import { CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatService, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; -import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { isRequestVM, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellEditType, CellKind, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; @@ -33,9 +34,10 @@ export function registerChatTitleActions() { icon: Codicon.thumbsup, toggled: CONTEXT_RESPONSE_VOTE.isEqualTo('up'), menu: { - id: MenuId.ChatTitle, + id: MenuId.ChatMessageTitle, group: 'navigation', - order: 1 + order: 1, + when: CONTEXT_RESPONSE } }); } @@ -72,9 +74,10 @@ export function registerChatTitleActions() { icon: Codicon.thumbsdown, toggled: CONTEXT_RESPONSE_VOTE.isEqualTo('down'), menu: { - id: MenuId.ChatTitle, + id: MenuId.ChatMessageTitle, group: 'navigation', - order: 2 + order: 2, + when: CONTEXT_RESPONSE } }); } @@ -110,10 +113,10 @@ export function registerChatTitleActions() { category: CHAT_CATEGORY, icon: Codicon.insert, menu: { - id: MenuId.ChatTitle, + id: MenuId.ChatMessageTitle, group: 'navigation', isHiddenByDefault: true, - when: NOTEBOOK_IS_ACTIVE_EDITOR + when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, CONTEXT_RESPONSE) } }); } @@ -172,6 +175,40 @@ export function registerChatTitleActions() { } } }); + + + registerAction2(class RemoveAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.chat.remove', + title: { + value: localize('chat.remove.label', "Remove Request and Response"), + original: 'Remove Request and Response' + }, + f1: false, + category: CHAT_CATEGORY, + icon: Codicon.x, + menu: { + id: MenuId.ChatMessageTitle, + group: 'navigation', + order: 2, + when: CONTEXT_REQUEST + } + }); + } + + run(accessor: ServicesAccessor, ...args: any[]) { + const item = args[0]; + if (!isRequestVM(item)) { + return; + } + + const chatService = accessor.get(IChatService); + if (item.providerRequestId) { + chatService.removeRequest(item.sessionId, item.providerRequestId); + } + } + }); } interface MarkdownContent { diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 89d03609f04..8ae6050e0c0 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -54,7 +54,7 @@ import { IChatCodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/a import { IChatCodeBlockInfo } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; -import { CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatReplyFollowup, IChatService, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestViewModel, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; @@ -193,12 +193,12 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - if (action instanceof MenuItemAction) { + if (action instanceof MenuItemAction && (action.item.id === 'workbench.action.chat.voteDown' || action.item.id === 'workbench.action.chat.voteUp')) { return scopedInstantiationService.createInstance(ChatVoteButton, action, options as IMenuEntryActionViewItemOptions); } @@ -218,6 +218,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer