From bd9880f692b53314cb74fefdeb628fd8b7469a5d Mon Sep 17 00:00:00 2001 From: colin-grant-work Date: Thu, 20 Mar 2025 09:08:37 -0600 Subject: [PATCH 01/15] Fix handling of additionalEventTypes in Widget.addKeyListener (#15210) --- CHANGELOG.md | 1 + packages/core/src/browser/widgets/widget.ts | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0fa4bed00ebb..f015c98e9af9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - [core] fixed version `@types/express` to `^4.17.21` and `@types/express-serve-static-core` to `5.0.4`. This might be required for adopters as well if they run into typing issues. [#15147](https://github.com/eclipse-theia/theia/pull/15147) - [core] migration from deprecated `phosphorJs` to actively maintained fork `Lumino` [#14320](https://github.com/eclipse-theia/theia/pull/14320) - Contributed on behalf of STMicroelectronics Adopters importing `@phosphor` packages now need to import from `@lumino`. CSS selectors refering to `.p-` classes now need to refer to `.lm-` classes. There are also minor code adaptations, for example now using `iconClass` instead of `icon` in Lumino commands. +- [core] typing of `addKeyListener` and `Widget.addKeyListener` corrected to reflect events for `additionalEventTypes`. Adopters declaring handlers explicitly expecting `KeyboardEvent` together with `additionalEventTypes` may need to update type declarations. [#15210] [Breaking Changes:](#breaking_changes_1.60.0) diff --git a/packages/core/src/browser/widgets/widget.ts b/packages/core/src/browser/widgets/widget.ts index 91adddbbdf1d4..7c0a70dd6c33c 100644 --- a/packages/core/src/browser/widgets/widget.ts +++ b/packages/core/src/browser/widgets/widget.ts @@ -207,10 +207,11 @@ export class BaseWidget extends Widget implements PreviewableWidget { this.toDisposeOnDetach.push(addEventListener(element, type, listener, useCapture)); } - protected addKeyListener( + protected addKeyListener( element: HTMLElement, keysOrKeyCodes: KeyCode.Predicate | KeysOrKeyCodes, - action: (event: KeyboardEvent) => boolean | void | Object, ...additionalEventTypes: K[]): void { + action: EventHandler, + ...additionalEventTypes: K[]): void { this.toDisposeOnDetach.push(addKeyListener(element, keysOrKeyCodes, action, ...additionalEventTypes)); } @@ -259,6 +260,8 @@ export function createIconButton(...classNames: string[]): HTMLSpanElement { // eslint-disable-next-line @typescript-eslint/no-explicit-any export type EventListener = (this: HTMLElement, event: HTMLElementEventMap[K]) => any; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type EventHandler = (event: HTMLElementEventMap[K]) => any; export interface EventListenerObject { handleEvent(evt: HTMLElementEventMap[K]): void; } @@ -277,10 +280,12 @@ export function addEventListener( ); } -export function addKeyListener( +export function addKeyListener( element: HTMLElement, keysOrKeyCodes: KeyCode.Predicate | KeysOrKeyCodes, - action: (event: KeyboardEvent) => boolean | void | Object, ...additionalEventTypes: K[]): Disposable { + action: EventHandler, + ...additionalEventTypes: K[]): Disposable { + type HandledEvent = Parameters[0]; const toDispose = new DisposableCollection(); const keyCodePredicate = (() => { @@ -293,7 +298,7 @@ export function addKeyListener( toDispose.push(addEventListener(element, 'keydown', e => { const kc = KeyCode.createKeyCode(e); if (keyCodePredicate(kc)) { - const result = action(e); + const result = action(e as HandledEvent); if (typeof result !== 'boolean' || result) { e.stopPropagation(); e.preventDefault(); @@ -302,9 +307,7 @@ export function addKeyListener( })); for (const type of additionalEventTypes) { toDispose.push(addEventListener(element, type, e => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const event = (type as any)['keydown']; - const result = action(event); + const result = action(e as HandledEvent); if (typeof result !== 'boolean' || result) { e.stopPropagation(); e.preventDefault(); From 49098c659e7dfcfa7f6dd2ec661312616472ae51 Mon Sep 17 00:00:00 2001 From: Florian Richter <77792630+mvtec-richter@users.noreply.github.com> Date: Fri, 21 Mar 2025 09:42:52 +0100 Subject: [PATCH 02/15] Add compile script to playwright package (#15122) Signed-off-by: Florian Richter --- examples/playwright/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/playwright/package.json b/examples/playwright/package.json index 0311bc4d77034..c40cffc7f2cdd 100644 --- a/examples/playwright/package.json +++ b/examples/playwright/package.json @@ -13,6 +13,7 @@ "homepage": "https://github.com/eclipse-theia/theia", "scripts": { "clean": "theiaext clean", + "compile": "theiaext compile", "build": "theiaext build && npm run playwright:install", "watch": "theiaext watch", "theia:start": "rimraf .tmp.cfg && cd ../browser && cross-env THEIA_CONFIG_DIR=$PWD/.tmp.cfg npm run start", From 9332521b9aa74bf534d45968905d37e30a13c3b5 Mon Sep 17 00:00:00 2001 From: Stefan Dirix Date: Fri, 21 Mar 2025 12:29:01 +0100 Subject: [PATCH 03/15] fix: autocomplete content in chat view (#15240) Fixes autocomplete content not showing up in inline editors like the ChatInput. With the Lumino migration we disabled 'fixedOverflowWidgets' in our Monaco editors, as Lumino widget styles leverages 'contain', influencing the position of autocomplete content. This is now reverted, and instead the 'contain' of Lumino widgets is forcefully disabled, fixing the autocomplete issue. fixes #15237 Co-authored-by: Eugen Neufeld --- packages/core/src/browser/style/index.css | 2 ++ packages/monaco/src/browser/monaco-diff-editor.ts | 6 +++--- packages/monaco/src/browser/monaco-editor-provider.ts | 4 ++-- packages/monaco/src/browser/monaco-editor.ts | 2 +- packages/monaco/src/browser/simple-monaco-editor.ts | 2 +- packages/output/src/browser/output-editor-factory.ts | 2 +- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/core/src/browser/style/index.css b/packages/core/src/browser/style/index.css index 63161f578a801..df81e7c510379 100644 --- a/packages/core/src/browser/style/index.css +++ b/packages/core/src/browser/style/index.css @@ -221,6 +221,8 @@ blockquote { .lm-Widget { font-size: var(--theia-ui-font-size1); + /** We override the contain of lm-Widget to make sure Monaco autocomplete etc. is correctly applied */ + contain: none !important; } .lm-Widget.lm-mod-hidden { diff --git a/packages/monaco/src/browser/monaco-diff-editor.ts b/packages/monaco/src/browser/monaco-diff-editor.ts index e01d5f3366edc..80f49e1eb0bed 100644 --- a/packages/monaco/src/browser/monaco-diff-editor.ts +++ b/packages/monaco/src/browser/monaco-diff-editor.ts @@ -77,7 +77,7 @@ export class MonacoDiffEditor extends MonacoEditor { } protected override create(options?: IDiffEditorConstructionOptions, override?: EditorServiceOverrides): Disposable { - options = { ...options, fixedOverflowWidgets: false }; + options = { ...options, fixedOverflowWidgets: true }; const instantiator = this.getInstantiatorWithOverrides(override); /** * @monaco-uplift. Should be guaranteed to work. @@ -101,7 +101,7 @@ export class MonacoDiffEditor extends MonacoEditor { const hasReachedSideBySideBreakpoint = leftEditor.contextKeyService .getContextKeyValue(EditorContextKeys.diffEditorRenderSideBySideInlineBreakpointReached.key); if (hasReachedSideBySideBreakpoint !== this.lastReachedSideBySideBreakpoint) { - leftEditor.updateOptions({ wordWrapOverride2: this.wordWrapOverride ?? hasReachedSideBySideBreakpoint ? 'off' : 'inherit' }); + leftEditor.updateOptions({ wordWrapOverride2: this.wordWrapOverride ?? hasReachedSideBySideBreakpoint ? 'off' : 'inherit' }); } this.lastReachedSideBySideBreakpoint = !!hasReachedSideBySideBreakpoint; } @@ -131,7 +131,7 @@ export class MonacoDiffEditor extends MonacoEditor { override handleVisibilityChanged(nowVisible: boolean): void { if (nowVisible) { - this.diffEditor.setModel({original: this.originalTextModel, modified: this.modifiedTextModel}); + this.diffEditor.setModel({ original: this.originalTextModel, modified: this.modifiedTextModel }); this.diffEditor.restoreViewState(this.savedDiffState); this.diffEditor.focus(); } else { diff --git a/packages/monaco/src/browser/monaco-editor-provider.ts b/packages/monaco/src/browser/monaco-editor-provider.ts index 55ad38c76f310..cb9d1c9e07a05 100644 --- a/packages/monaco/src/browser/monaco-editor-provider.ts +++ b/packages/monaco/src/browser/monaco-editor-provider.ts @@ -409,7 +409,7 @@ export class MonacoEditorProvider { overviewRulerBorder: false, scrollBeyondLastLine: false, renderLineHighlight: 'none', - fixedOverflowWidgets: false, + fixedOverflowWidgets: true, acceptSuggestionOnEnter: 'smart', minimap: { enabled: false @@ -421,7 +421,7 @@ export class MonacoEditorProvider { options = { scrollBeyondLastLine: true, overviewRulerLanes: 2, - fixedOverflowWidgets: false, + fixedOverflowWidgets: true, minimap: { enabled: false }, renderSideBySide: false, readOnly: true, diff --git a/packages/monaco/src/browser/monaco-editor.ts b/packages/monaco/src/browser/monaco-editor.ts index 07495415678a6..8607987b563a2 100644 --- a/packages/monaco/src/browser/monaco-editor.ts +++ b/packages/monaco/src/browser/monaco-editor.ts @@ -174,7 +174,7 @@ export class MonacoEditor extends MonacoEditorServices implements TextEditor { const combinedOptions = { ...options, lightbulb: { enabled: ShowLightbulbIconMode.On }, - fixedOverflowWidgets: false, + fixedOverflowWidgets: true, scrollbar: { useShadows: false, verticalHasArrows: false, diff --git a/packages/monaco/src/browser/simple-monaco-editor.ts b/packages/monaco/src/browser/simple-monaco-editor.ts index 8c0ac82ee1cae..c247eed050729 100644 --- a/packages/monaco/src/browser/simple-monaco-editor.ts +++ b/packages/monaco/src/browser/simple-monaco-editor.ts @@ -89,7 +89,7 @@ export class SimpleMonacoEditor extends MonacoEditorServices implements Disposab const combinedOptions = { ...options, lightbulb: { enabled: ShowLightbulbIconMode.On }, - fixedOverflowWidgets: false, + fixedOverflowWidgets: true, automaticLayout: true, scrollbar: { useShadows: false, diff --git a/packages/output/src/browser/output-editor-factory.ts b/packages/output/src/browser/output-editor-factory.ts index 47adcdfc3de87..595272f00a5fb 100644 --- a/packages/output/src/browser/output-editor-factory.ts +++ b/packages/output/src/browser/output-editor-factory.ts @@ -47,7 +47,7 @@ export class OutputEditorFactory implements MonacoEditorFactory { ...defaultOptions, overviewRulerLanes: 3, lineNumbersMinChars: 3, - fixedOverflowWidgets: false, + fixedOverflowWidgets: true, wordWrap: 'off', lineNumbers: 'off', glyphMargin: false, From 8547f54fcd43ba67b9afebf5a32023ac909e1701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=A4der?= Date: Mon, 24 Mar 2025 14:10:00 +0100 Subject: [PATCH 04/15] Limit the width of tab-bar rows to 100% of parent (#15260) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #15254 Contributed on behalf of STMicroelectronics Signed-off-by: Thomas Mäder --- packages/core/src/browser/style/tabs.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/browser/style/tabs.css b/packages/core/src/browser/style/tabs.css index 9f3ca3f01eb33..f34ab93219f21 100644 --- a/packages/core/src/browser/style/tabs.css +++ b/packages/core/src/browser/style/tabs.css @@ -459,7 +459,7 @@ } .theia-tabBar-breadcrumb-row { - min-width: 100%; + width: 100%; } .lm-TabBar.theia-tabBar-multirow[data-orientation="horizontal"] { @@ -470,7 +470,7 @@ .lm-TabBar[data-orientation="horizontal"] .theia-tabBar-tab-row { display: flex; flex-flow: row nowrap; - min-width: 100%; + width: 100%; } .lm-TabBar[data-orientation="vertical"] .theia-tabBar-tab-row { From 52a76263b78a9f6ecf113dddc6b30ec06e7463f3 Mon Sep 17 00:00:00 2001 From: Jonas Helming Date: Mon, 24 Mar 2025 14:58:46 +0100 Subject: [PATCH 05/15] Refine AI setting descriptions (#15250) --- .../ai-configuration/ai-configuration-preferences.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ai-ide/src/browser/ai-configuration/ai-configuration-preferences.ts b/packages/ai-ide/src/browser/ai-configuration/ai-configuration-preferences.ts index 74b9e7668fb73..e78d71f1a6f9e 100644 --- a/packages/ai-ide/src/browser/ai-configuration/ai-configuration-preferences.ts +++ b/packages/ai-ide/src/browser/ai-configuration/ai-configuration-preferences.ts @@ -28,21 +28,21 @@ export const AiConfigurationPreferences: PreferenceSchema = { 'ai-features.agentSettings.details': { type: 'null', markdownDescription: nls.localize('theia/ai/ide/agent-description', - 'Additional settings for AI agents can be configured using the [AI Configuration View]({0}).', + 'Configure AI agent settings including enablement, LLM selection, prompt template customization, and custom agent creation in the [AI Configuration View]({0}).', 'command:aiConfiguration:open' ) }, 'ai-features.promptTemplates.details': { type: 'null', markdownDescription: nls.localize('theia/ai/ide/prompt-template-description', - 'Additional AI prompt template settings can be configured using the [AI Configuration View]({0}).', + 'Select prompt variants and customize prompt templates for AI agents in the [AI Configuration View]({0}).', 'command:aiConfiguration:open' ) }, - 'ai-features.models.details': { + 'ai-features.modelSelection.details': { type: 'null', - markdownDescription: nls.localize('theia/ai/ide/prompt-template-description', - 'Additional settings for AI models can be configured using the [AI Configuration View]({0}).', + markdownDescription: nls.localize('theia/ai/ide/model-selection-description', + 'Choose which Large Language Models (LLMs) are used by each AI agent in the [AI Configuration View]({0}).', 'command:aiConfiguration:open' ) } From a470d3366e7b8f7c1df156af35255f76b6026c7e Mon Sep 17 00:00:00 2001 From: Jonas Helming Date: Mon, 24 Mar 2025 19:52:53 +0100 Subject: [PATCH 06/15] Add shortcut for adding the current file to the AI chat context (#15252) --- .../browser/theia-variable-contribution.ts | 121 ++++++++++++------ 1 file changed, 81 insertions(+), 40 deletions(-) diff --git a/packages/ai-core/src/browser/theia-variable-contribution.ts b/packages/ai-core/src/browser/theia-variable-contribution.ts index cb68c27c6b452..f501650891974 100644 --- a/packages/ai-core/src/browser/theia-variable-contribution.ts +++ b/packages/ai-core/src/browser/theia-variable-contribution.ts @@ -19,6 +19,14 @@ import { inject, injectable } from '@theia/core/shared/inversify'; import { VariableRegistry, VariableResolverService } from '@theia/variable-resolver/lib/browser'; import { AIVariableContribution, AIVariableResolver, AIVariableService, AIVariableResolutionRequest, AIVariableContext, ResolvedAIVariable } from '../common'; +/** + * Mapping configuration for a Theia variable to one or more AI variables + */ +interface VariableMapping { + name?: string; + description?: string; +} + /** * Integrates the Theia VariableRegistry with the Theia AI VariableService */ @@ -36,33 +44,52 @@ export class TheiaVariableContribution implements AIVariableContribution, AIVari @inject(FrontendApplicationStateService) protected readonly stateService: FrontendApplicationStateService; - // Map original variable name to new name and description. If a mapped value is not present then the original will be kept. + // Map original variable name to one or more mappings with new name and description. // Only variables present in this map are registered. - protected variableRenameMap: Map = new Map([ - ['file', { - name: 'currentAbsoluteFilePath', description: nls.localize('theia/ai/core/variable-contribution/currentAbsoluteFilePath', 'The absolute path of the \ - currently opened file. Please note that most agents will expect a relative file path (relative to the current workspace).') - }], - ['selectedText', { - description: nls.localize('theia/ai/core/variable-contribution/currentSelectedText', 'The plain text that is currently selected in the \ - opened file. This excludes the information where the content is coming from. Please note that most agents will work better with a relative file path \ - (relative to the current workspace).') - }], - ['currentText', { - name: 'currentFileContent', description: nls.localize('theia/ai/core/variable-contribution/currentFileContent', 'The plain content of the \ - currently opened file. This excludes the information where the content is coming from. Please note that most agents will work better with a relative file path \ - (relative to the current workspace).') - }], - ['relativeFile', { - name: 'currentRelativeFilePath', description: nls.localize('theia/ai/core/variable-contribution/currentRelativeFilePath', 'The relative path of the \ - currently opened file.') - }], - ['relativeFileDirname', { - name: 'currentRelativeDirPath', description: nls.localize('theia/ai/core/variable-contribution/currentRelativeDirPath', 'The relative path of the directory \ - containing the currently opened file.') - }], - ['lineNumber', {}], - ['workspaceFolder', {}] + protected variableRenameMap: Map = new Map([ + ['file', [ + { + name: 'currentAbsoluteFilePath', + description: nls.localize('theia/ai/core/variable-contribution/currentAbsoluteFilePath', 'The absolute path of the \ + currently opened file. Please note that most agents will expect a relative file path (relative to the current workspace).') + } + ]], + ['selectedText', [ + { + description: nls.localize('theia/ai/core/variable-contribution/currentSelectedText', 'The plain text that is currently selected in the \ + opened file. This excludes the information where the content is coming from. Please note that most agents will work better with a relative file path \ + (relative to the current workspace).') + } + ]], + ['currentText', [ + { + name: 'currentFileContent', + description: nls.localize('theia/ai/core/variable-contribution/currentFileContent', 'The plain content of the \ + currently opened file. This excludes the information where the content is coming from. Please note that most agents will work better with a relative file path \ + (relative to the current workspace).') + } + ]], + ['relativeFile', [ + { + name: 'currentRelativeFilePath', + description: nls.localize('theia/ai/core/variable-contribution/currentRelativeFilePath', 'The relative path of the \ + currently opened file.') + }, + { + name: '_f', + description: nls.localize('theia/ai/core/variable-contribution/dotRelativePath', 'Short reference to the relative path of the \ + currently opened file (\'currentRelativeFilePath\').') + } + ]], + ['relativeFileDirname', [ + { + name: 'currentRelativeDirPath', + description: nls.localize('theia/ai/core/variable-contribution/currentRelativeDirPath', 'The relative path of the directory \ + containing the currently opened file.') + } + ]], + ['lineNumber', [{}]], + ['workspaceFolder', [{}]] ]); registerVariables(service: AIVariableService): void { @@ -73,25 +100,39 @@ export class TheiaVariableContribution implements AIVariableContribution, AIVari if (!this.variableRenameMap.has(variable.name)) { return; // Do not register variables not part of the map } - const mapping = this.variableRenameMap.get(variable.name)!; - const newName = (mapping.name && mapping.name.trim() !== '') ? mapping.name : variable.name; - const newDescription = (mapping.description && mapping.description.trim() !== '') ? mapping.description - : (variable.description && variable.description.trim() !== '' ? variable.description - : nls.localize('theia/ai/core/variable-contribution/builtInVariable', 'Theia Built-in Variable')); - - service.registerResolver({ - id: `${TheiaVariableContribution.THEIA_PREFIX}${variable.name}`, - name: newName, - description: newDescription - }, this); + + const mappings = this.variableRenameMap.get(variable.name)!; + + // Register each mapping for this variable + mappings.forEach((mapping, index) => { + const newName = (mapping.name && mapping.name.trim() !== '') ? mapping.name : variable.name; + const newDescription = (mapping.description && mapping.description.trim() !== '') ? mapping.description + : (variable.description && variable.description.trim() !== '' ? variable.description + : nls.localize('theia/ai/core/variable-contribution/builtInVariable', 'Theia Built-in Variable')); + + // For multiple mappings of the same variable, add a suffix to the ID to make it unique + const idSuffix = mappings.length > 1 ? `-${index}` : ''; + const id = `${TheiaVariableContribution.THEIA_PREFIX}${variable.name}${idSuffix}`; + + service.registerResolver({ + id, + name: newName, + description: newDescription + }, this); + }); }); }); } protected toTheiaVariable(request: AIVariableResolutionRequest): string { - // Remove the THEIA_PREFIX if present before constructing the variable string - const variableId = request.variable.id.startsWith(TheiaVariableContribution.THEIA_PREFIX) ? request.variable.id.slice(TheiaVariableContribution.THEIA_PREFIX.length) : - request.variable.id; + // Extract the base variable name by removing the THEIA_PREFIX and any potential index suffix + let variableId = request.variable.id; + if (variableId.startsWith(TheiaVariableContribution.THEIA_PREFIX)) { + variableId = variableId.slice(TheiaVariableContribution.THEIA_PREFIX.length); + // Remove any potential index suffix (e.g., -0, -1) + variableId = variableId.replace(/-\d+$/, ''); + } + return `\${${variableId}${request.arg ? ':' + request.arg : ''}}`; } From abae3331300ea81cc21ad77335df2b3330700b5f Mon Sep 17 00:00:00 2001 From: fanyipin <570524947@qq.com> Date: Tue, 25 Mar 2025 17:06:45 +0800 Subject: [PATCH 07/15] fix: Pin node-abi version to 3.x due to Node.js 22+ requirement in 4.x (#15212) * pins the node-abi version to resolve the compatibility issue where node-abi 4.x requires Node.js version 22 or higher * remove unused package node-abi @types/node-abi * update package-lock.json after delete node-abi --- dev-packages/application-manager/package.json | 4 +--- package-lock.json | 11 +---------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/dev-packages/application-manager/package.json b/dev-packages/application-manager/package.json index 2ac9d04b2f832..b5b599d793620 100644 --- a/dev-packages/application-manager/package.json +++ b/dev-packages/application-manager/package.json @@ -49,7 +49,6 @@ "ignore-loader": "^0.1.2", "less": "^3.0.3", "mini-css-extract-plugin": "^2.6.1", - "node-abi": "*", "node-loader": "^2.0.0", "path-browserify": "^1.0.1", "semver": "^7.5.4", @@ -74,8 +73,7 @@ } }, "devDependencies": { - "@theia/ext-scripts": "1.59.0", - "@types/node-abi": "*" + "@theia/ext-scripts": "1.59.0" }, "nyc": { "extends": "../../configs/nyc.json" diff --git a/package-lock.json b/package-lock.json index 5e37d75bce94a..312d6d97300e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -89,7 +89,6 @@ "ignore-loader": "^0.1.2", "less": "^3.0.3", "mini-css-extract-plugin": "^2.6.1", - "node-abi": "*", "node-loader": "^2.0.0", "path-browserify": "^1.0.1", "semver": "^7.5.4", @@ -106,8 +105,7 @@ "yargs": "^15.3.1" }, "devDependencies": { - "@theia/ext-scripts": "1.59.0", - "@types/node-abi": "*" + "@theia/ext-scripts": "1.59.0" }, "peerDependencies": { "@theia/electron": "*" @@ -6377,13 +6375,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@types/node-abi": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/node-abi/-/node-abi-3.0.3.tgz", - "integrity": "sha512-5oos6sivyXcDEuVC5oX3+wLwfgrGZu4NIOn826PGAjPCHsqp2zSPTGU7H1Tv+GZBOiDUY3nBXY1MdaofSEt4fw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node-fetch": { "version": "2.6.12", "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", From e93f8159c3b48449861c5d1e2c57ac7a13654c2b Mon Sep 17 00:00:00 2001 From: Mark Sujew Date: Tue, 25 Mar 2025 11:35:33 +0100 Subject: [PATCH 08/15] Prevent plugin host localization errors (#15268) --- .../src/hosted/node/hosted-plugin-localization-service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/plugin-ext/src/hosted/node/hosted-plugin-localization-service.ts b/packages/plugin-ext/src/hosted/node/hosted-plugin-localization-service.ts index 877b321568012..bc0dcd0950087 100644 --- a/packages/plugin-ext/src/hosted/node/hosted-plugin-localization-service.ts +++ b/packages/plugin-ext/src/hosted/node/hosted-plugin-localization-service.ts @@ -379,11 +379,11 @@ function coerceLocalizations(translations: Record function localizePackage(value: unknown, translations: PackageTranslation, callback: (key: string, defaultValue: string) => string): unknown { if (typeof value === 'string') { let result = value; - if (value.startsWith('%') && value.endsWith('%')) { + if (value.length > 2 && value.startsWith('%') && value.endsWith('%')) { const key = value.slice(1, -1); - if (translations.translation) { + if (translations.translation && key in translations.translation) { result = translations.translation[key]; - } else if (translations.default) { + } else if (translations.default && key in translations.default) { result = callback(key, translations.default[key]); } } From 56414b97c19b0a2986ba902c4ac758b991f14b37 Mon Sep 17 00:00:00 2001 From: Jonas Helming Date: Tue, 25 Mar 2025 11:45:07 +0100 Subject: [PATCH 09/15] Allow to add all MCP functions via prompt fragment (#15270) fixed #14952 --- .../src/browser/mcp-frontend-service.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/ai-mcp/src/browser/mcp-frontend-service.ts b/packages/ai-mcp/src/browser/mcp-frontend-service.ts index 90445d618d48a..6e2fb399c03a1 100644 --- a/packages/ai-mcp/src/browser/mcp-frontend-service.ts +++ b/packages/ai-mcp/src/browser/mcp-frontend-service.ts @@ -15,16 +15,20 @@ // ***************************************************************************** import { injectable, inject } from '@theia/core/shared/inversify'; import { MCPServer, MCPServerManager } from '../common/mcp-server-manager'; -import { ToolInvocationRegistry, ToolRequest } from '@theia/ai-core'; +import { ToolInvocationRegistry, ToolRequest, PromptService } from '@theia/ai-core'; @injectable() export class MCPFrontendService { + @inject(MCPServerManager) protected readonly mcpServerManager: MCPServerManager; @inject(ToolInvocationRegistry) protected readonly toolInvocationRegistry: ToolInvocationRegistry; + @inject(PromptService) + protected readonly promptService: PromptService; + async startServer(serverName: string): Promise { await this.mcpServerManager.startServer(serverName); this.registerTools(serverName); @@ -43,10 +47,28 @@ export class MCPFrontendService { toolRequests.forEach(toolRequest => this.toolInvocationRegistry.registerTool(toolRequest) ); + + this.createPromptTemplate(serverName, toolRequests); + } + + private getPromptTemplateId(serverName: string): string { + return `mcp_${serverName}_tools`; + } + + protected createPromptTemplate(serverName: string, toolRequests: ToolRequest[]): void { + const templateId = this.getPromptTemplateId(serverName); + const functionIds = toolRequests.map(tool => `~{${tool.id}}`); + const template = functionIds.join('\n'); + + this.promptService.storePromptTemplate({ + id: templateId, + template + }); } async stopServer(serverName: string): Promise { this.toolInvocationRegistry.unregisterAllTools(`mcp_${serverName}`); + this.promptService.removePrompt(this.getPromptTemplateId(serverName)); await this.mcpServerManager.stopServer(serverName); } From 6623f3c355adec7631cb2351ee62bcc243dfbace Mon Sep 17 00:00:00 2001 From: Remi Schnekenburger Date: Tue, 25 Mar 2025 14:15:20 +0100 Subject: [PATCH 10/15] [vscode] Support keepWhitespace in SnippetTextEdit and insertSnippet (#15176) fixes #15131 Contributed on behalf of STMicroelectronics Signed-off-by: Remi Schnekenburger --- .../plugin-ext/src/common/plugin-api-rpc.ts | 8 +++++-- .../src/main/browser/text-editor-main.ts | 24 ++++++++++++++----- .../src/main/browser/text-editors-main.ts | 4 ++-- .../plugin-ext/src/plugin/type-converters.ts | 5 ++-- packages/plugin-ext/src/plugin/types-impl.ts | 1 + packages/plugin/src/theia.d.ts | 20 +++++++++++++++- 6 files changed, 49 insertions(+), 13 deletions(-) diff --git a/packages/plugin-ext/src/common/plugin-api-rpc.ts b/packages/plugin-ext/src/common/plugin-api-rpc.ts index d028daf31f4f0..f0eefe072f966 100644 --- a/packages/plugin-ext/src/common/plugin-api-rpc.ts +++ b/packages/plugin-ext/src/common/plugin-api-rpc.ts @@ -1242,6 +1242,10 @@ export interface ApplyEditsOptions extends UndoStopOptions { setEndOfLine: EndOfLine | undefined; } +export interface SnippetEditOptions extends UndoStopOptions { + keepWhitespace?: boolean; +} + export interface ThemeColor { id: string; } @@ -1346,7 +1350,7 @@ export interface TextEditorsMain { $trySetSelections(id: string, selections: Selection[]): Promise; $tryApplyEdits(id: string, modelVersionId: number, edits: SingleEditOperation[], opts: ApplyEditsOptions): Promise; $tryApplyWorkspaceEdit(workspaceEditDto: WorkspaceEditDto, metadata?: WorkspaceEditMetadataDto): Promise; - $tryInsertSnippet(id: string, template: string, selections: Range[], opts: UndoStopOptions): Promise; + $tryInsertSnippet(id: string, template: string, selections: Range[], opts: SnippetEditOptions): Promise; $save(uri: UriComponents): PromiseLike; $saveAs(uri: UriComponents): PromiseLike; $saveAll(includeUntitled?: boolean): Promise; @@ -1546,7 +1550,7 @@ export interface WorkspaceFileEditDto { export interface WorkspaceTextEditDto { resource: UriComponents; modelVersionId?: number; - textEdit: TextEdit & { insertAsSnippet?: boolean }; + textEdit: TextEdit & { insertAsSnippet?: boolean, keepWhitespace?: boolean }; metadata?: WorkspaceEditEntryMetadataDto; } export namespace WorkspaceTextEditDto { diff --git a/packages/plugin-ext/src/main/browser/text-editor-main.ts b/packages/plugin-ext/src/main/browser/text-editor-main.ts index 0ce4f478ed8a7..67ac7ccaa3907 100644 --- a/packages/plugin-ext/src/main/browser/text-editor-main.ts +++ b/packages/plugin-ext/src/main/browser/text-editor-main.ts @@ -26,7 +26,7 @@ import { TextEditorRevealType, SingleEditOperation, ApplyEditsOptions, - UndoStopOptions, + SnippetEditOptions, DecorationOptions } from '../../common/plugin-api-rpc'; import { Range } from '../../common/plugin-api-rpc-model'; @@ -281,7 +281,7 @@ export class TextEditorMain implements Disposable { return true; } - insertSnippet(template: string, ranges: Range[], opts: UndoStopOptions): boolean { + insertSnippet(template: string, ranges: Range[], opts: SnippetEditOptions): boolean { const snippetController: SnippetController2 | null | undefined = this.editor?.getControl().getContribution('snippetController2'); if (!snippetController || !this.editor) { return false; } @@ -290,7 +290,13 @@ export class TextEditorMain implements Disposable { this.editor.getControl().setSelections(selections); this.editor.focus(); - snippetController.insert(template, 0, 0, opts.undoStopBefore, opts.undoStopAfter); + snippetController.insert(template, { + undoStopBefore: opts.undoStopBefore, + undoStopAfter: opts.undoStopAfter, + adjustWhitespace: !opts.keepWhitespace, + overwriteBefore: 0, + overwriteAfter: 0 + }); return true; } @@ -324,11 +330,17 @@ export class TextEditorMain implements Disposable { } } +interface SnippetInsertOptions { + overwriteBefore: number, + overwriteAfter: number, + undoStopBefore: boolean, + undoStopAfter: boolean, + adjustWhitespace: boolean +} + // TODO move to monaco typings! interface SnippetController2 extends monaco.editor.IEditorContribution { - insert(template: string, - overwriteBefore: number, overwriteAfter: number, - undoStopBefore: boolean, undoStopAfter: boolean): void; + insert(template: string, options?: Partial): void; finish(): void; cancel(): void; dispose(): void; diff --git a/packages/plugin-ext/src/main/browser/text-editors-main.ts b/packages/plugin-ext/src/main/browser/text-editors-main.ts index 93c2d820c54ef..262a704a75048 100644 --- a/packages/plugin-ext/src/main/browser/text-editors-main.ts +++ b/packages/plugin-ext/src/main/browser/text-editors-main.ts @@ -23,7 +23,6 @@ import { TextEditorRevealType, SingleEditOperation, ApplyEditsOptions, - UndoStopOptions, DecorationRenderOptions, ThemeDecorationInstanceRenderOptions, DecorationOptions, @@ -31,6 +30,7 @@ import { WorkspaceNotebookCellEditDto, DocumentsMain, WorkspaceEditMetadataDto, + SnippetEditOptions, } from '../../common/plugin-api-rpc'; import { Range, TextDocumentShowOptions } from '../../common/plugin-api-rpc-model'; import { EditorsAndDocumentsMain } from './editors-and-documents-main'; @@ -157,7 +157,7 @@ export class TextEditorsMainImpl implements TextEditorsMain, Disposable { } } - $tryInsertSnippet(id: string, template: string, ranges: Range[], opts: UndoStopOptions): Promise { + $tryInsertSnippet(id: string, template: string, ranges: Range[], opts: SnippetEditOptions): Promise { if (!this.editorsAndDocuments.getEditor(id)) { return Promise.reject(disposed(`TextEditor(${id})`)); } diff --git a/packages/plugin-ext/src/plugin/type-converters.ts b/packages/plugin-ext/src/plugin/type-converters.ts index b3be5296552cb..8f8124611909c 100644 --- a/packages/plugin-ext/src/plugin/type-converters.ts +++ b/packages/plugin-ext/src/plugin/type-converters.ts @@ -342,11 +342,12 @@ export function fromTextEdit(edit: theia.TextEdit): model.TextEdit { }; } -function fromSnippetTextEdit(edit: theia.SnippetTextEdit): model.TextEdit & { insertAsSnippet?: boolean } { +function fromSnippetTextEdit(edit: theia.SnippetTextEdit): model.TextEdit & { insertAsSnippet?: boolean, keepWhitespace?: boolean } { return { text: edit.snippet.value, range: fromRange(edit.range), - insertAsSnippet: true + insertAsSnippet: true, + keepWhitespace: edit.keepWhitespace }; } diff --git a/packages/plugin-ext/src/plugin/types-impl.ts b/packages/plugin-ext/src/plugin/types-impl.ts index 3025aa0fa51e4..e8a674873686c 100644 --- a/packages/plugin-ext/src/plugin/types-impl.ts +++ b/packages/plugin-ext/src/plugin/types-impl.ts @@ -1341,6 +1341,7 @@ export class NotebookRange implements theia.NotebookRange { export class SnippetTextEdit implements theia.SnippetTextEdit { range: Range; snippet: SnippetString; + keepWhitespace?: boolean; static isSnippetTextEdit(thing: unknown): thing is SnippetTextEdit { return thing instanceof SnippetTextEdit || isObject(thing) diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index 54fec79781393..3fd6f90b2aea8 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -1148,7 +1148,20 @@ export module '@theia/plugin' { * @return A promise that resolves with a value indicating if the snippet could be inserted. Note that the promise does not signal * that the snippet is completely filled-in or accepted. */ - insertSnippet(snippet: SnippetString, location?: Position | Range | Position[] | Range[], options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable; + insertSnippet(snippet: SnippetString, location?: Position | Range | Position[] | Range[], options?: { + /** + * Add undo stop before making the edits. + */ + readonly undoStopBefore: boolean; + /** + * Add undo stop after making the edits. + */ + readonly undoStopAfter: boolean; + /** + * Keep whitespace of the {@link SnippetString.value} as is. + */ + readonly keepWhitespace?: boolean; + }): Thenable; /** * Adds a set of decorations to the text editor. If a set of decorations already exists with @@ -16321,6 +16334,11 @@ export module '@theia/plugin' { */ snippet: SnippetString; + /** + * Whether the snippet edit should be applied with existing whitespace preserved. + */ + keepWhitespace?: boolean; + /** * Create a new snippet edit. * From 7aaa024f73fc7310e7a8f8d7bf49ac8248e9efe5 Mon Sep 17 00:00:00 2001 From: colin-grant-work Date: Tue, 25 Mar 2025 08:00:13 -0600 Subject: [PATCH 11/15] Workspace Symbols: Pass multiple classnames as array of strings (#15244) --- packages/monaco/src/browser/workspace-symbol-command.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/monaco/src/browser/workspace-symbol-command.ts b/packages/monaco/src/browser/workspace-symbol-command.ts index e4e56fcc5823b..b4e15dc1d8b04 100644 --- a/packages/monaco/src/browser/workspace-symbol-command.ts +++ b/packages/monaco/src/browser/workspace-symbol-command.ts @@ -156,7 +156,7 @@ export class WorkspaceSymbolCommand implements QuickAccessProvider, CommandContr if (!kind) { return undefined; } - return [`codicon ${inline ? 'inline' : 'block'} codicon-symbol-${kind.toLowerCase() || 'property'}`]; + return ['codicon', `${inline ? 'inline' : 'block'}`, `codicon-symbol-${kind.toLowerCase() || 'property'}`]; } private openURL(uri: URI, start: Position, end: Position): void { From 2a2c0bab5bfc907cb0b58611a4e190fcc37408a7 Mon Sep 17 00:00:00 2001 From: colin-grant-work Date: Tue, 25 Mar 2025 08:02:14 -0600 Subject: [PATCH 12/15] Dispose of child InstantiationServices in MonacoEditor (#15246) --- packages/monaco/src/browser/monaco-editor.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/monaco/src/browser/monaco-editor.ts b/packages/monaco/src/browser/monaco-editor.ts index 8607987b563a2..f736e7f211cf0 100644 --- a/packages/monaco/src/browser/monaco-editor.ts +++ b/packages/monaco/src/browser/monaco-editor.ts @@ -198,7 +198,9 @@ export class MonacoEditor extends MonacoEditorServices implements TextEditor { const instantiator = StandaloneServices.get(IInstantiationService); if (override) { const overrideServices = new ServiceCollection(...override); - return instantiator.createChild(overrideServices); + const child = instantiator.createChild(overrideServices); + this.toDispose.push(child); + return child; } return instantiator; } From 9878419ee6a6d9a86564d0e0a95277b196957830 Mon Sep 17 00:00:00 2001 From: Colin Grant Date: Thu, 20 Mar 2025 12:31:20 -0600 Subject: [PATCH 13/15] Ensure editor created before services used --- .../browser/monaco-editor-peek-view-widget.ts | 52 +++++++++++++++ .../browser/dirty-diff/dirty-diff-widget.ts | 63 ++++++++++--------- 2 files changed, 87 insertions(+), 28 deletions(-) diff --git a/packages/monaco/src/browser/monaco-editor-peek-view-widget.ts b/packages/monaco/src/browser/monaco-editor-peek-view-widget.ts index 4c5d90b455c34..94bd47fc1f3d9 100644 --- a/packages/monaco/src/browser/monaco-editor-peek-view-widget.ts +++ b/packages/monaco/src/browser/monaco-editor-peek-view-widget.ts @@ -90,6 +90,14 @@ export class MonacoEditorPeekViewWidget { return this._actionbarWidget; } + fillContainer(container: HTMLElement): void { + super._fillContainer(container); + } + + protected override _fillContainer(container: HTMLElement): void { + that.fillContainer(container); + } + fillHead(container: HTMLElement, noCloseAction?: boolean): void { super._fillHead(container, noCloseAction); } @@ -137,6 +145,26 @@ export class MonacoEditorPeekViewWidget { protected override revealRange(range: monaco.Range, isLastLine: boolean): void { that.doRevealRange(that.editor['m2p'].asRange(range), isLastLine); } + + getBodyElement(): HTMLDivElement | undefined { + return this._bodyElement; + } + + setBodyElement(element: HTMLDivElement | undefined): void { + this._bodyElement = element; + } + + getHeadElement(): HTMLDivElement | undefined { + return this._headElement; + } + + setHeadElement(element: HTMLDivElement | undefined): void { + this._headElement = element; + } + + override setCssClass(className: string, classToReplace?: string | undefined): void { + super.setCssClass(className, classToReplace); + } }( editor.getControl() as unknown as ICodeEditor, Object.assign({}, options, this.convertStyles(styles)), @@ -185,6 +213,10 @@ export class MonacoEditorPeekViewWidget { return action; } + protected fillContainer(container: HTMLElement): void { + this.delegate.fillContainer(container) + } + protected fillHead(container: HTMLElement, noCloseAction?: boolean): void { this.delegate.fillHead(container, noCloseAction); } @@ -209,6 +241,26 @@ export class MonacoEditorPeekViewWidget { this.delegate.doRevealRange(this.editor['p2m'].asRange(range), isLastLine); } + protected get bodyElement(): HTMLDivElement | undefined { + return this.delegate.getBodyElement(); + } + + protected set bodyElement(element: HTMLDivElement | undefined) { + this.delegate.setBodyElement(element); + } + + protected get headElement(): HTMLDivElement | undefined { + return this.delegate.getHeadElement(); + } + + protected set headElement(element: HTMLDivElement | undefined) { + this.delegate.setHeadElement(element); + } + + protected setCssClass(className: string, classToReplace?: string | undefined): void { + this.delegate.setCssClass(className, classToReplace); + } + private convertStyles(styles: MonacoEditorPeekViewWidget.Styles): IPeekViewStyles { return { frameColor: this.convertColor(styles.frameColor), diff --git a/packages/scm/src/browser/dirty-diff/dirty-diff-widget.ts b/packages/scm/src/browser/dirty-diff/dirty-diff-widget.ts index 4fe5d5b5e58e9..76ab3878d7690 100644 --- a/packages/scm/src/browser/dirty-diff/dirty-diff-widget.ts +++ b/packages/scm/src/browser/dirty-diff/dirty-diff-widget.ts @@ -48,8 +48,8 @@ export class DirtyDiffWidget implements Disposable { private readonly onDidCloseEmitter = new Emitter(); readonly onDidClose: Event = this.onDidCloseEmitter.event; protected index: number = -1; - private peekView?: DirtyDiffPeekView; - private diffEditorPromise?: Promise; + private peekView: DirtyDiffPeekView; + private diffEditorPromise: Promise; constructor( @inject(DirtyDiffWidgetProps) protected readonly props: DirtyDiffWidgetProps, @@ -90,16 +90,16 @@ export class DirtyDiffWidget implements Disposable { return this.index; } - showChange(index: number): void { - this.checkCreated(); + async showChange(index: number): Promise { + await this.checkCreated(); if (index >= 0 && index < this.changes.length) { this.index = index; this.showCurrentChange(); } } - showNextChange(): void { - this.checkCreated(); + async showNextChange(): Promise { + await this.checkCreated(); const index = this.index; const length = this.changes.length; if (length > 0 && (index < 0 || length > 1)) { @@ -108,8 +108,8 @@ export class DirtyDiffWidget implements Disposable { } } - showPreviousChange(): void { - this.checkCreated(); + async showPreviousChange(): Promise { + await this.checkCreated(); const index = this.index; const length = this.changes.length; if (length > 0 && (index < 0 || length > 1)) { @@ -119,7 +119,7 @@ export class DirtyDiffWidget implements Disposable { } async getContentWithSelectedChanges(predicate: (change: Change, index: number, changes: readonly Change[]) => boolean): Promise { - this.checkCreated(); + await this.checkCreated(); const changes = this.changes.filter(predicate); const { diffEditor } = await this.diffEditorPromise!; const diffEditorModel = diffEditor.getModel()!; @@ -134,9 +134,9 @@ export class DirtyDiffWidget implements Disposable { protected showCurrentChange(): void { this.peekView!.setTitle(this.computePrimaryHeading(), this.computeSecondaryHeading()); const { previousRange, currentRange } = this.changes[this.index]; - this.peekView!.show(Position.create(LineRange.getEndPosition(currentRange).line, 0), + this.peekView.show(Position.create(LineRange.getEndPosition(currentRange).line, 0), this.computeHeightInLines()); - this.diffEditorPromise!.then(({ diffEditor }) => { + this.diffEditorPromise.then(({ diffEditor }) => { let startLine = LineRange.getStartPosition(currentRange).line; let endLine = LineRange.getEndPosition(currentRange).line; if (LineRange.isEmpty(currentRange)) { // the change is a removal @@ -174,10 +174,8 @@ export class DirtyDiffWidget implements Disposable { return Math.min(changeHeightInLines + /* padding */ 8, Math.floor(editorHeightInLines / 3)); } - protected checkCreated(): void { - if (!this.peekView) { - throw new Error('create() method needs to be called first.'); - } + protected async checkCreated(): Promise { + await this.diffEditorPromise; } } @@ -250,7 +248,7 @@ function applyChanges(changes: readonly Change[], original: monaco.editor.ITextM class DirtyDiffPeekView extends MonacoEditorPeekViewWidget { - private diffEditorPromise?: Promise; + private diffEditor?: MonacoDiffEditor; private height?: number; constructor(readonly widget: DirtyDiffWidget) { @@ -259,12 +257,16 @@ class DirtyDiffPeekView extends MonacoEditorPeekViewWidget { override async create(): Promise { try { + this.bodyElement = document.createElement('div'); + this.bodyElement.classList.add('body'); + const diffEditor = await this.widget.editorProvider.createEmbeddedDiffEditor(this.editor, this.bodyElement, this.widget.previousRevisionUri); + this.diffEditor = diffEditor; + this.toDispose.push(diffEditor); super.create(); - const diffEditor = await this.diffEditorPromise!; return new Promise(resolve => { - // setTimeout is needed here because the non-side-by-side diff editor might still not have created the view zones; - // otherwise, the first change shown might not be properly revealed in the diff editor. - // see also https://github.com/microsoft/vscode/blob/b30900b56c4b3ca6c65d7ab92032651f4cb23f15/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts#L248 + // setTimeout is needed here because the non-side-by-side diff editor might still not have created the view zones; + // otherwise, the first change shown might not be properly revealed in the diff editor. + // see also https://github.com/microsoft/vscode/blob/b30900b56c4b3ca6c65d7ab92032651f4cb23f15/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts#L248 const disposable = diffEditor.diffEditor.onDidUpdateDiff(() => setTimeout(() => { resolve(diffEditor); disposable.dispose(); @@ -329,15 +331,20 @@ class DirtyDiffPeekView extends MonacoEditorPeekViewWidget { () => this.dispose()); } - protected override fillHead(container: HTMLElement): void { - super.fillHead(container, true); + protected override fillContainer(container: HTMLElement): void { + this.setCssClass('peekview-widget'); + + this.headElement = document.createElement('div'); + this.headElement.classList.add('head'); + + container.appendChild(this.headElement); + container.appendChild(this.bodyElement!); + + this.fillHead(this.headElement); } - protected override fillBody(container: HTMLElement): void { - this.diffEditorPromise = this.widget.editorProvider.createEmbeddedDiffEditor(this.editor, container, this.widget.previousRevisionUri).then(diffEditor => { - this.toDispose.push(diffEditor); - return diffEditor; - }); + protected override fillHead(container: HTMLElement): void { + super.fillHead(container, true); } protected override doLayoutBody(height: number, width: number): void { @@ -355,7 +362,7 @@ class DirtyDiffPeekView extends MonacoEditorPeekViewWidget { } private layout(height: number, width: number): void { - this.diffEditorPromise?.then(({ diffEditor }) => diffEditor.layout({ height, width })); + this.diffEditor?.diffEditor.layout({ height, width }); } protected override doRevealRange(range: Range): void { From c22d8bcc560f29f1fac9f0287bd5ee8bdfee963a Mon Sep 17 00:00:00 2001 From: Colin Grant Date: Tue, 25 Mar 2025 08:06:34 -0600 Subject: [PATCH 14/15] Lint --- packages/monaco/src/browser/monaco-editor-peek-view-widget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/monaco/src/browser/monaco-editor-peek-view-widget.ts b/packages/monaco/src/browser/monaco-editor-peek-view-widget.ts index 94bd47fc1f3d9..2510e375710f2 100644 --- a/packages/monaco/src/browser/monaco-editor-peek-view-widget.ts +++ b/packages/monaco/src/browser/monaco-editor-peek-view-widget.ts @@ -214,7 +214,7 @@ export class MonacoEditorPeekViewWidget { } protected fillContainer(container: HTMLElement): void { - this.delegate.fillContainer(container) + this.delegate.fillContainer(container); } protected fillHead(container: HTMLElement, noCloseAction?: boolean): void { From 39e0f4c43620ede07203e78e65ed0a5f3b2a49d4 Mon Sep 17 00:00:00 2001 From: Colin Grant Date: Tue, 25 Mar 2025 08:50:28 -0600 Subject: [PATCH 15/15] Close editor on successful action, not any change --- packages/scm/src/browser/dirty-diff/dirty-diff-navigator.ts | 1 - packages/scm/src/browser/dirty-diff/dirty-diff-widget.ts | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/scm/src/browser/dirty-diff/dirty-diff-navigator.ts b/packages/scm/src/browser/dirty-diff/dirty-diff-navigator.ts index d765797337617..a3a24f037fefc 100644 --- a/packages/scm/src/browser/dirty-diff/dirty-diff-navigator.ts +++ b/packages/scm/src/browser/dirty-diff/dirty-diff-navigator.ts @@ -135,7 +135,6 @@ export class DirtyDiffController implements Disposable { handleDirtyDiffUpdate(dirtyDiff: DirtyDiffUpdate): void { if (dirtyDiff.editor === this.editor) { - this.closeWidget(); this.dirtyDiff = dirtyDiff; } } diff --git a/packages/scm/src/browser/dirty-diff/dirty-diff-widget.ts b/packages/scm/src/browser/dirty-diff/dirty-diff-widget.ts index 76ab3878d7690..28afe8b93a7ae 100644 --- a/packages/scm/src/browser/dirty-diff/dirty-diff-widget.ts +++ b/packages/scm/src/browser/dirty-diff/dirty-diff-widget.ts @@ -315,8 +315,10 @@ class DirtyDiffPeekView extends MonacoEditorPeekViewWidget { if (item instanceof ActionMenuNode) { const { command, id, label, icon, when } = item; if (icon && menuCommandExecutor.isVisible(menuPath, command, this.widget) && (!when || contextKeyService.match(when))) { + // Close editor on successful contributed action. + // https://github.com/microsoft/vscode/blob/11b1500e0a2e8b5ba12e98a3905f9d120b8646a0/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts#L356-L361 this.addAction(id, label, icon, menuCommandExecutor.isEnabled(menuPath, command, this.widget), () => { - menuCommandExecutor.executeCommand(menuPath, command, this.widget); + menuCommandExecutor.executeCommand(menuPath, command, this.widget).then(() => this.dispose()); }); } }