From 17ba136619d5f656e8b5a9dc22a353fed7c144ee Mon Sep 17 00:00:00 2001 From: andreamah Date: Tue, 27 Aug 2024 17:35:26 -0700 Subject: [PATCH 01/47] Closed notebook search blocks text search Fixes #226859 --- .../browser/notebookSearch/notebookSearchService.ts | 11 +++++------ .../workbench/services/search/common/queryBuilder.ts | 4 +++- src/vs/workbench/services/search/common/search.ts | 1 + .../workbench/services/search/common/searchService.ts | 3 +++ 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts b/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts index 4ef1cdae76e..5265cf2c785 100644 --- a/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts +++ b/src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchService.ts @@ -117,22 +117,21 @@ export class NotebookSearchService implements INotebookSearchService { } private async doesFileExist(includes: string[], folderQueries: IFolderQuery[], token: CancellationToken): Promise { - const promises: Promise[] = includes.map(async includePattern => { + const promises: Promise[] = includes.map(async includePattern => { const query = this.queryBuilder.file(folderQueries.map(e => e.folder), { includePattern: includePattern.startsWith('/') ? includePattern : '**/' + includePattern, // todo: find cleaner way to ensure that globs match all appropriate filetypes - exists: true + exists: true, + onlyFileScheme: true, }); return this.searchService.fileSearch( query, token ).then((ret) => { - if (!ret.limitHit) { - throw Error('File not found'); - } + return !!ret.limitHit; }); }); - return Promise.any(promises).then(() => true).catch(() => false); + return Promise.any(promises); } private async getClosedNotebookResults(textQuery: ITextQuery, scannedFiles: ResourceSet, token: CancellationToken): Promise { diff --git a/src/vs/workbench/services/search/common/queryBuilder.ts b/src/vs/workbench/services/search/common/queryBuilder.ts index b249184ce97..5692ab093c9 100644 --- a/src/vs/workbench/services/search/common/queryBuilder.ts +++ b/src/vs/workbench/services/search/common/queryBuilder.ts @@ -92,6 +92,7 @@ interface ICommonQueryBuilderOptions { disregardSearchExcludeSettings?: boolean; ignoreSymlinks?: boolean; onlyOpenEditors?: boolean; + onlyFileScheme?: boolean; } export interface IFileQueryBuilderOptions extends ICommonQueryBuilderOptions { @@ -259,7 +260,8 @@ export class QueryBuilder { excludePattern: excludeSearchPathsInfo.pattern, includePattern: includeSearchPathsInfo.pattern, onlyOpenEditors: options.onlyOpenEditors, - maxResults: options.maxResults + maxResults: options.maxResults, + onlyFileScheme: options.onlyFileScheme }; if (options.onlyOpenEditors) { diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 5f6283c3346..79b0e06ee8b 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -100,6 +100,7 @@ export interface ICommonQueryProps { maxResults?: number; usingSearchPaths?: boolean; + onlyFileScheme?: boolean; } export interface IFileQueryProps extends ICommonQueryProps { diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index f4e35c525e1..b58dc567e87 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -271,6 +271,9 @@ export class SearchService extends Disposable implements ISearchService { return []; } await Promise.all([...fqs.keys()].map(async scheme => { + if (query.onlyFileScheme && scheme !== Schemas.file) { + return; + } const schemeFQs = fqs.get(scheme)!; let provider = this.getSearchProvider(query.type).get(scheme); From df7cea5892b29822bf5f772683791b60ba1d14b4 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 28 Aug 2024 09:27:45 +0200 Subject: [PATCH 02/47] SCM - fix hover layout regression (#226933) --- src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts index ba24fdfaf6d..69060d9dd4c 100644 --- a/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts @@ -314,7 +314,7 @@ class HistoryItemRenderer implements ITreeRenderer labels.includes(l.title)); - if (historyItemLabels) { + if (historyItemLabels.length > 0) { const historyItemGroupLocalColor = colorTheme.getColor(historyItemGroupLocal); const historyItemGroupRemoteColor = colorTheme.getColor(historyItemGroupRemote); const historyItemGroupBaseColor = colorTheme.getColor(historyItemGroupBase); From 54cad31687aa64eb7d4437f2970777441a39fdcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 28 Aug 2024 10:12:38 +0200 Subject: [PATCH 03/47] cleanup web html pages (#216609) --- build/gulpfile.vscode.web.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/build/gulpfile.vscode.web.js b/build/gulpfile.vscode.web.js index b6a26fc4845..f78a2c64354 100644 --- a/build/gulpfile.vscode.web.js +++ b/build/gulpfile.vscode.web.js @@ -58,7 +58,6 @@ const vscodeWebResourceIncludes = isESM() ? [ // Workbench 'out-build/vs/{base,platform,editor,workbench}/**/*.{svg,png,jpg,mp3}', - 'out-build/vs/code/browser/workbench/*.html', 'out-build/vs/base/browser/ui/codicons/codicon/**/*.ttf', 'out-build/vs/**/markdown.css', @@ -67,7 +66,9 @@ const vscodeWebResourceIncludes = isESM() ? [ // Webview 'out-build/vs/workbench/contrib/webview/browser/pre/*.js', - 'out-build/vs/workbench/contrib/webview/browser/pre/*.html', + 'out-build/vs/workbench/contrib/webview/browser/pre/index.html', + 'out-build/vs/workbench/contrib/webview/browser/pre/index-no-csp.html', + 'out-build/vs/workbench/contrib/webview/browser/pre/fake.html', // Extension Worker 'out-build/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html', @@ -227,7 +228,8 @@ function packageTask(sourceFolderName, destinationFolderName) { const src = gulp.src(sourceFolderName + '/**', { base: '.' }) .pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + sourceFolderName), 'out'); })); - const extensions = gulp.src('.build/web/extensions/**', { base: '.build/web', dot: true }); + const extensions = gulp.src('.build/web/extensions/**', { base: '.build/web', dot: true }) + .pipe(filter(['**', '!**/*.{html,htm}'], { dot: true })); const sources = es.merge(src, extensions) .pipe(filter(['**', '!**/*.js.map'], { dot: true })); From 21b0cfbbcea929aacd62d7323994650b1d8acaee Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 28 Aug 2024 10:25:19 +0200 Subject: [PATCH 04/47] fix https://github.com/microsoft/vscode-copilot/issues/7701 (#226942) --- .../browser/inlineChatController.ts | 2 +- .../inlineChat/browser/inlineChatWidget.ts | 2 +- .../inlineChat/browser/media/inlineChat.css | 21 ++++++++++++++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 29b4dee9719..4e0e05b6b53 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -990,7 +990,7 @@ export class InlineChatController implements IEditorContribution { // ---- controller API showSaveHint(): void { - const status = localize('savehint', "Accept or discard changes to continue saving"); + const status = localize('savehint', "Accept or discard changes to continue saving."); this._ui.value.zone.widget.updateStatus(status, { classes: ['warn'] }); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 252534e6643..91a572c2ee6 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -385,7 +385,7 @@ export class InlineChatWidget { } protected _getExtraHeight(): number { - return 4 /* padding */ + 2 /*border*/ + 4 /*shadow*/; + return 2 /*border*/ + 4 /*shadow*/; } get value(): string { diff --git a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css index f209158f8fe..f79ae5a6a9f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/media/inlineChat.css @@ -20,7 +20,7 @@ } .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part { - padding: 4px 6px 0 6px; + padding: 2px 6px 0 6px; } .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part .interactive-execute-toolbar { @@ -32,6 +32,12 @@ border-radius: 2px; } + +.monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-input-part .interactive-input-followups .interactive-session-followups { + margin: 2px 0 0 4px; +} + + .monaco-workbench .inline-chat .chat-widget .interactive-session .interactive-list { padding: 4px 0 0 0; } @@ -70,7 +76,15 @@ display: flex; justify-content: space-between; align-items: center; - padding: 6px 6px 0 6px + padding-left: 6px; + padding-right: 6px; +} + +.monaco-workbench .inline-chat > .status { + .label, + .actions { + padding-top: 6px; + } } .monaco-workbench .inline-chat .status .actions.hidden { @@ -92,7 +106,8 @@ .monaco-workbench .inline-chat .status .label.status { margin-left: auto; - padding: 0 6px; + padding-right: 6px; + padding-left: 6px; } .monaco-workbench .inline-chat .status .label.hidden, From 977073ddf16164220364e890892ef60eab099d09 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 28 Aug 2024 11:05:48 +0200 Subject: [PATCH 05/47] fix https://github.com/microsoft/vscode/issues/226717 (#226943) --- .../electron-sandbox/actions/voiceChatActions.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index bffc94d794e..b0455f490d4 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -941,12 +941,6 @@ export class StopReadAloud extends Action2 { when: ScopedChatSynthesisInProgress, group: 'navigation', order: -1 - }, - { - id: MENU_INLINE_CHAT_WIDGET_SECONDARY, - when: ScopedChatSynthesisInProgress, - group: 'navigation', - order: -1 } ] }); @@ -980,6 +974,15 @@ export class StopReadChatItemAloud extends Action2 { CONTEXT_RESPONSE_FILTERED.negate() // but not when response is filtered ), group: 'navigation' + }, + { + id: MENU_INLINE_CHAT_WIDGET_SECONDARY, + when: ContextKeyExpr.and( + ScopedChatSynthesisInProgress, // only when in progress + CONTEXT_RESPONSE, // only for responses + CONTEXT_RESPONSE_FILTERED.negate() // but not when response is filtered + ), + group: 'navigation' } ] }); From 2a0e70d6c6226ef5021285b22cfbe07c929d64bc Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 28 Aug 2024 11:12:39 +0200 Subject: [PATCH 06/47] title - tweak window controls container (#226941) --- .../parts/titlebar/media/titlebarpart.css | 50 ++++++++++--------- .../browser/parts/titlebar/titlebarPart.ts | 34 ++++++++----- .../parts/titlebar/titlebarPart.ts | 10 ++-- 3 files changed, 54 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 15afa9df6bf..937428df156 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -138,11 +138,11 @@ color: var(--vscode-titleBar-activeForeground); } -.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { +.monaco-workbench .part.titlebar.inactive > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { color: var(--vscode-titleBar-inactiveForeground); } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center > .monaco-toolbar > .monaco-action-bar > .actions-container > .action-item > .action-label { color: inherit; } @@ -182,7 +182,7 @@ text-overflow: ellipsis; } -.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center.multiple { +.monaco-workbench .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center.multiple { justify-content: flex-start; padding: 0 12px; } @@ -280,7 +280,7 @@ border-left: 1px solid transparent; } -/* Window Controls (Minimize, Max/Restore, Close) */ +/* Window Controls Container */ .monaco-workbench .part.titlebar .window-controls-container { display: flex; flex-grow: 0; @@ -292,7 +292,12 @@ height: 100%; } -/* Web WCO Sizing/Ordering */ +.monaco-workbench.fullscreen .part.titlebar .window-controls-container { + display: none; + background-color: transparent; +} + +/* Window Controls Container Web: Apply WCO environment variables (https://developer.mozilla.org/en-US/docs/Web/CSS/env#titlebar-area-x) */ .monaco-workbench.web .part.titlebar .titlebar-right .window-controls-container { width: calc(100vw - env(titlebar-area-width, 100vw) - env(titlebar-area-x, 0px)); height: env(titlebar-area-height, 35px); @@ -311,29 +316,31 @@ order: 1; } -/* Desktop Windows/Linux Window Controls*/ -.monaco-workbench:not(.web):not(.mac) .part.titlebar .window-controls-container.primary { +/* Window Controls Container Desktop: apply zoom friendly size */ +.monaco-workbench:not(.web):not(.mac) .part.titlebar .window-controls-container { width: calc(138px / var(--zoom-factor, 1)); } -.monaco-workbench:not(.web):not(.mac) .part.titlebar .titlebar-container.counter-zoom .window-controls-container.primary { +.monaco-workbench:not(.web):not(.mac) .part.titlebar .titlebar-container.counter-zoom .window-controls-container { width: 138px; } +.monaco-workbench.linux:not(.web) .part.titlebar .window-controls-container.wco-enabled { + width: calc(100px / var(--zoom-factor, 1)); /* TODO@bpasero TODO@benibenj this should not be hardcoded (https://github.com/microsoft/vscode/issues/226804) */ +} + +.monaco-workbench.linux:not(.web) .part.titlebar .titlebar-container.counter-zoom .window-controls-container.wco-enabled { + width: 100px; /* TODO@bpasero TODO@benibenj this should not be hardcoded (https://github.com/microsoft/vscode/issues/226804) */ +} + .monaco-workbench:not(.web):not(.mac) .part.titlebar .titlebar-container:not(.counter-zoom) .window-controls-container * { zoom: calc(1 / var(--zoom-factor, 1)); } -/* Desktop macOS Window Controls */ -.monaco-workbench:not(.web).mac .part.titlebar .window-controls-container.primary { +.monaco-workbench:not(.web).mac .part.titlebar .window-controls-container { width: 70px; } -.monaco-workbench.fullscreen .part.titlebar .window-controls-container { - display: none; - background-color: transparent; -} - /* Window Control Icons */ .monaco-workbench .part.titlebar .window-controls-container > .window-icon { display: flex; @@ -342,6 +349,11 @@ height: 100%; width: 46px; font-size: 16px; + color: var(--vscode-titleBar-activeForeground); +} + +.monaco-workbench .part.titlebar.inactive .window-controls-container > .window-icon { + color: var(--vscode-titleBar-inactiveForeground); } .monaco-workbench .part.titlebar .window-controls-container > .window-icon::before { @@ -455,11 +467,3 @@ border-radius: 16px; text-align: center; } - -.monaco-workbench .part.titlebar .window-controls-container .window-icon { - color: var(--vscode-titleBar-activeForeground); -} - -.monaco-workbench .part.titlebar.inactive .window-controls-container .window-icon { - color: var(--vscode-titleBar-inactiveForeground); -} diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 9dd7d5550bf..169a4c588cf 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -249,7 +249,7 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { //#endregion protected rootContainer!: HTMLElement; - protected primaryWindowControls: HTMLElement | undefined; + protected windowControlsContainer: HTMLElement | undefined; protected dragRegion: HTMLElement | undefined; private title!: HTMLElement; @@ -476,21 +476,31 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart { this.createActionToolBarMenus(); } - let primaryControlLocation = isMacintosh ? 'left' : 'right'; - if (isMacintosh && isNative) { + // Window Controls Container + if (!hasNativeTitlebar(this.configurationService, this.titleBarStyle)) { + let windowControlsLocation = isMacintosh ? 'left' : 'right'; + if (isMacintosh && isNative) { - // Check if the locale is RTL, macOS will move traffic lights in RTL locales - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo + // Check if the locale is RTL, macOS will move traffic lights in RTL locales + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo - const localeInfo = new Intl.Locale(platformLocale) as any; - if (localeInfo?.textInfo?.direction === 'rtl') { - primaryControlLocation = 'right'; + const localeInfo = new Intl.Locale(platformLocale) as any; + if (localeInfo?.textInfo?.direction === 'rtl') { + windowControlsLocation = 'right'; + } } - } - if (!hasNativeTitlebar(this.configurationService, this.titleBarStyle)) { - this.primaryWindowControls = append(primaryControlLocation === 'left' ? this.leftContent : this.rightContent, $('div.window-controls-container.primary')); - append(primaryControlLocation === 'left' ? this.rightContent : this.leftContent, $('div.window-controls-container.secondary')); + if (isMacintosh && isNative && windowControlsLocation === 'left') { + // macOS native: controls are on the left and the container is not needed to make room + // for something, except for web where a custom menu being supported). not putting the + // container helps with allowing to move the window when clicking very close to the + // window control buttons. + } else { + this.windowControlsContainer = append(windowControlsLocation === 'left' ? this.leftContent : this.rightContent, $('div.window-controls-container')); + if (isWCOEnabled()) { + this.windowControlsContainer.classList.add('wco-enabled'); + } + } } // Context menu over title bar: depending on the OS and the location of the click this will either be diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts index 12609c51916..2fa6f984217 100644 --- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts @@ -156,17 +156,17 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { }))); } - // Window Controls (Native Windows/Linux) - if (!isMacintosh && !hasNativeTitlebar(this.configurationService) && !isWCOEnabled() && this.primaryWindowControls) { + // Window Controls (Native Linux when WCO is disabled) + if (isLinux && !hasNativeTitlebar(this.configurationService) && !isWCOEnabled() && this.windowControlsContainer) { // Minimize - const minimizeIcon = append(this.primaryWindowControls, $('div.window-icon.window-minimize' + ThemeIcon.asCSSSelector(Codicon.chromeMinimize))); + const minimizeIcon = append(this.windowControlsContainer, $('div.window-icon.window-minimize' + ThemeIcon.asCSSSelector(Codicon.chromeMinimize))); this._register(addDisposableListener(minimizeIcon, EventType.CLICK, () => { this.nativeHostService.minimizeWindow({ targetWindowId }); })); // Restore - this.maxRestoreControl = append(this.primaryWindowControls, $('div.window-icon.window-max-restore')); + this.maxRestoreControl = append(this.windowControlsContainer, $('div.window-icon.window-max-restore')); this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async () => { const maximized = await this.nativeHostService.isMaximized({ targetWindowId }); if (maximized) { @@ -177,7 +177,7 @@ export class NativeTitlebarPart extends BrowserTitlebarPart { })); // Close - const closeIcon = append(this.primaryWindowControls, $('div.window-icon.window-close' + ThemeIcon.asCSSSelector(Codicon.chromeClose))); + const closeIcon = append(this.windowControlsContainer, $('div.window-icon.window-close' + ThemeIcon.asCSSSelector(Codicon.chromeClose))); this._register(addDisposableListener(closeIcon, EventType.CLICK, () => { this.nativeHostService.closeWindow({ targetWindowId }); })); From c9f093d529eb72847f2aa1a6c2a6f663d0782fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 28 Aug 2024 14:24:52 +0200 Subject: [PATCH 07/47] Revert "cleanup web html pages (#216609)" This reverts commit 54cad31687aa64eb7d4437f2970777441a39fdcc. --- build/gulpfile.vscode.web.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/build/gulpfile.vscode.web.js b/build/gulpfile.vscode.web.js index f78a2c64354..b6a26fc4845 100644 --- a/build/gulpfile.vscode.web.js +++ b/build/gulpfile.vscode.web.js @@ -58,6 +58,7 @@ const vscodeWebResourceIncludes = isESM() ? [ // Workbench 'out-build/vs/{base,platform,editor,workbench}/**/*.{svg,png,jpg,mp3}', + 'out-build/vs/code/browser/workbench/*.html', 'out-build/vs/base/browser/ui/codicons/codicon/**/*.ttf', 'out-build/vs/**/markdown.css', @@ -66,9 +67,7 @@ const vscodeWebResourceIncludes = isESM() ? [ // Webview 'out-build/vs/workbench/contrib/webview/browser/pre/*.js', - 'out-build/vs/workbench/contrib/webview/browser/pre/index.html', - 'out-build/vs/workbench/contrib/webview/browser/pre/index-no-csp.html', - 'out-build/vs/workbench/contrib/webview/browser/pre/fake.html', + 'out-build/vs/workbench/contrib/webview/browser/pre/*.html', // Extension Worker 'out-build/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html', @@ -228,8 +227,7 @@ function packageTask(sourceFolderName, destinationFolderName) { const src = gulp.src(sourceFolderName + '/**', { base: '.' }) .pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + sourceFolderName), 'out'); })); - const extensions = gulp.src('.build/web/extensions/**', { base: '.build/web', dot: true }) - .pipe(filter(['**', '!**/*.{html,htm}'], { dot: true })); + const extensions = gulp.src('.build/web/extensions/**', { base: '.build/web', dot: true }); const sources = es.merge(src, extensions) .pipe(filter(['**', '!**/*.js.map'], { dot: true })); From 4fb85226c4736ffd212e7210b62b53e538815c8f Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:33:16 +0200 Subject: [PATCH 08/47] Use instant hover for Radio buttons (#226940) --- src/vs/base/browser/ui/radio/radio.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/base/browser/ui/radio/radio.ts b/src/vs/base/browser/ui/radio/radio.ts index 2f57e4ace30..5ab1edc41b2 100644 --- a/src/vs/base/browser/ui/radio/radio.ts +++ b/src/vs/base/browser/ui/radio/radio.ts @@ -11,7 +11,7 @@ import { $ } from 'vs/base/browser/dom'; import { IHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegate'; import { Button } from 'vs/base/browser/ui/button/button'; import { DisposableMap, DisposableStore } from 'vs/base/common/lifecycle'; -import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; +import { createInstantHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory'; export interface IRadioStyles { readonly activeForeground?: string; @@ -53,7 +53,7 @@ export class Radio extends Widget { constructor(opts: IRadioOptions) { super(); - this.hoverDelegate = opts.hoverDelegate ?? getDefaultHoverDelegate('element'); + this.hoverDelegate = opts.hoverDelegate ?? this._register(createInstantHoverDelegate()); this.domNode = $('.monaco-custom-radio'); this.domNode.setAttribute('role', 'radio'); From f89c4523267c0c522047ef2ecef10f560ac59ef8 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 28 Aug 2024 15:02:44 +0200 Subject: [PATCH 09/47] Inline chat fixes (#226960) Only undo changes when an error happened and make sure it happens before recomputing hunks Mark hunks with overlapping manual edits as accepted so that they don't get double applied or double rejected Make sure `recompute` works correctly for diff to no-diff transition - fixes https://github.com/microsoft/vscode-copilot/issues/7537 - fixes https://github.com/microsoft/vscode-copilot/issues/7719 - fixes https://github.com/microsoft/vscode-copilot/issues/7764 --- .../browser/inlineChatController.ts | 3 +- .../inlineChat/browser/inlineChatSession.ts | 74 +++++++++------- ..._should_be_easier_to_undo_esc__7537.1.snap | 13 +++ ..._should_be_easier_to_undo_esc__7537.2.snap | 6 ++ .../test/browser/inlineChatController.test.ts | 4 +- .../test/browser/inlineChatSession.test.ts | 88 +++++++++++++++++++ 6 files changed, 150 insertions(+), 38 deletions(-) create mode 100644 src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.1.snap create mode 100644 src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.2.snap diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 4e0e05b6b53..823ea678aa7 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -741,7 +741,7 @@ export class InlineChatController implements IEditorContribution { await responsePromise.p; await progressiveEditsQueue.whenIdle(); - if (response.isCanceled) { + if (response.result?.errorDetails) { await this._session.undoChangesUntil(response.requestId); } @@ -758,7 +758,6 @@ export class InlineChatController implements IEditorContribution { if (response.result?.errorDetails) { // - await this._session.undoChangesUntil(response.requestId); } else if (response.response.value.length === 0) { // empty -> show message diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index a3a6a25a86a..82c395d21e2 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -360,27 +360,30 @@ export class HunkData { // mirror textModelN changes to textModel0 execept for those that // overlap with a hunk - type HunkRangePair = { rangeN: Range; range0: Range }; + type HunkRangePair = { rangeN: Range; range0: Range; markAccepted: () => void }; const hunkRanges: HunkRangePair[] = []; const ranges0: Range[] = []; - for (const { textModelNDecorations, textModel0Decorations, state } of this._data.values()) { + for (const entry of this._data.values()) { - if (state === HunkState.Pending) { + if (entry.state === HunkState.Pending) { // pending means the hunk's changes aren't "sync'd" yet - for (let i = 1; i < textModelNDecorations.length; i++) { - const rangeN = this._textModelN.getDecorationRange(textModelNDecorations[i]); - const range0 = this._textModel0.getDecorationRange(textModel0Decorations[i]); + for (let i = 1; i < entry.textModelNDecorations.length; i++) { + const rangeN = this._textModelN.getDecorationRange(entry.textModelNDecorations[i]); + const range0 = this._textModel0.getDecorationRange(entry.textModel0Decorations[i]); if (rangeN && range0) { - hunkRanges.push({ rangeN, range0 }); + hunkRanges.push({ + rangeN, range0, + markAccepted: () => entry.state = HunkState.Accepted + }); } } - } else if (state === HunkState.Accepted) { + } else if (entry.state === HunkState.Accepted) { // accepted means the hunk's changes are also in textModel0 - for (let i = 1; i < textModel0Decorations.length; i++) { - const range = this._textModel0.getDecorationRange(textModel0Decorations[i]); + for (let i = 1; i < entry.textModel0Decorations.length; i++) { + const range = this._textModel0.getDecorationRange(entry.textModel0Decorations[i]); if (range) { ranges0.push(range); } @@ -399,16 +402,20 @@ export class HunkData { let pendingChangesLen = 0; - for (const { rangeN, range0 } of hunkRanges) { - if (rangeN.getEndPosition().isBefore(Range.getStartPosition(change.range))) { + for (const entry of hunkRanges) { + if (entry.rangeN.getEndPosition().isBefore(Range.getStartPosition(change.range))) { // pending hunk _before_ this change. When projecting into textModel0 we need to // subtract that. Because diffing is relaxed it might include changes that are not // actual insertions/deletions. Therefore we need to take the length of the original // range into account. - pendingChangesLen += this._textModelN.getValueLengthInRange(rangeN); - pendingChangesLen -= this._textModel0.getValueLengthInRange(range0); - - } else if (Range.areIntersectingOrTouching(rangeN, change.range)) { + pendingChangesLen += this._textModelN.getValueLengthInRange(entry.rangeN); + pendingChangesLen -= this._textModel0.getValueLengthInRange(entry.range0); + + } else if (Range.areIntersectingOrTouching(entry.rangeN, change.range)) { + // an edit overlaps with a (pending) hunk. We take this as a signal + // to mark the hunk as accepted and to ignore the edit. The range of the hunk + // will be up-to-date because of decorations created for them + entry.markAccepted(); isOverlapping = true; break; @@ -447,24 +454,23 @@ export class HunkData { diff ??= await this._editorWorkerService.computeDiff(this._textModel0.uri, this._textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: Number.MAX_SAFE_INTEGER, computeMoves: false }, 'advanced'); - if (!diff || diff.changes.length === 0) { - // return new HunkData([], session); - return; - } - - // merge changes neighboring changes - const mergedChanges = [diff.changes[0]]; - for (let i = 1; i < diff.changes.length; i++) { - const lastChange = mergedChanges[mergedChanges.length - 1]; - const thisChange = diff.changes[i]; - if (thisChange.modified.startLineNumber - lastChange.modified.endLineNumberExclusive <= HunkData._HUNK_THRESHOLD) { - mergedChanges[mergedChanges.length - 1] = new DetailedLineRangeMapping( - lastChange.original.join(thisChange.original), - lastChange.modified.join(thisChange.modified), - (lastChange.innerChanges ?? []).concat(thisChange.innerChanges ?? []) - ); - } else { - mergedChanges.push(thisChange); + let mergedChanges: DetailedLineRangeMapping[] = []; + + if (diff && diff.changes.length > 0) { + // merge changes neighboring changes + mergedChanges = [diff.changes[0]]; + for (let i = 1; i < diff.changes.length; i++) { + const lastChange = mergedChanges[mergedChanges.length - 1]; + const thisChange = diff.changes[i]; + if (thisChange.modified.startLineNumber - lastChange.modified.endLineNumberExclusive <= HunkData._HUNK_THRESHOLD) { + mergedChanges[mergedChanges.length - 1] = new DetailedLineRangeMapping( + lastChange.original.join(thisChange.original), + lastChange.modified.join(thisChange.modified), + (lastChange.innerChanges ?? []).concat(thisChange.innerChanges ?? []) + ); + } else { + mergedChanges.push(thisChange); + } } } diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.1.snap b/src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.1.snap new file mode 100644 index 00000000000..a0379e041b9 --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.1.snap @@ -0,0 +1,13 @@ +export function fib(n) { + if (n <= 0) return 0; + if (n === 1) return 0; + if (n === 2) return 1; + + let a = 0, b = 1, c; + for (let i = 3; i <= n; i++) { + c = a + b; + a = b; + b = c; + } + return b; +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.2.snap b/src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.2.snap new file mode 100644 index 00000000000..3d44a421300 --- /dev/null +++ b/src/vs/workbench/contrib/inlineChat/test/browser/__snapshots__/InlineChatSession_Apply_Code_s_preview_should_be_easier_to_undo_esc__7537.2.snap @@ -0,0 +1,6 @@ +export function fib(n) { + if (n <= 0) return 0; + if (n === 1) return 0; + if (n === 2) return 1; + return fib(n - 1) + fib(n - 2); +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index e5b27c5c017..ca2b5478650 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -779,7 +779,7 @@ suite('InteractiveChatController', function () { }); - test('Stopping/cancelling a request should undo its changes', async function () { + test('Stopping/cancelling a request should NOT undo its changes', async function () { model.setValue('World'); @@ -819,7 +819,7 @@ suite('InteractiveChatController', function () { chatService.cancelCurrentRequestForSession(ctrl.chatWidget.viewModel!.model.sessionId); assert.strictEqual(await p2, undefined); - assert.strictEqual(model.getValue(), 'World'); + assert.strictEqual(model.getValue(), 'HelloWorld'); // CANCEL just stops the request and progressive typing but doesn't undo }); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts index af92e83ea50..82734a99ef7 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -60,6 +60,8 @@ import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/co import { NullWorkbenchAssignmentService } from 'vs/workbench/services/assignment/test/common/nullAssignmentService'; import { ILanguageModelToolsService } from 'vs/workbench/contrib/chat/common/languageModelToolsService'; import { MockLanguageModelToolsService } from 'vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService'; +import { IChatRequestModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { assertSnapshot } from 'vs/base/test/common/snapshot'; suite('InlineChatSession', function () { @@ -487,4 +489,90 @@ suite('InlineChatSession', function () { inlineChatSessionService.releaseSession(session); }); + + test('Pressing Escape after inline chat errored with "response filtered" leaves document dirty #7764', async function () { + + const origValue = `class Foo { + private onError(error: string): void { + if (/The request timed out|The network connection was lost/i.test(error)) { + return; + } + + error = error.replace(/See https:\/\/github\.com\/Squirrel\/Squirrel\.Mac\/issues\/182 for more information/, 'This might mean the application was put on quarantine by macOS. See [this link](https://github.com/microsoft/vscode/issues/7426#issuecomment-425093469) for more information'); + + this.notificationService.notify({ + severity: Severity.Error, + message: error, + source: nls.localize('update service', "Update Service"), + }); + } +}`; + model.setValue(origValue); + + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + const fakeRequest = new class extends mock() { + override get id() { return 'one'; } + }; + session.markModelVersion(fakeRequest); + + assert.strictEqual(editor.getModel().getLineCount(), 15); + + await makeEditAsAi([EditOperation.replace(new Range(7, 1, 7, Number.MAX_SAFE_INTEGER), `error = error.replace( + /See https:\/\/github\.com\/Squirrel\/Squirrel\.Mac\/issues\/182 for more information/, + 'This might mean the application was put on quarantine by macOS. See [this link](https://github.com/microsoft/vscode/issues/7426#issuecomment-425093469) for more information' + );`)]); + + assert.strictEqual(editor.getModel().getLineCount(), 18); + + // called when a response errors out + await session.undoChangesUntil(fakeRequest.id); + await session.hunkData.recompute({ applied: 0, sha1: 'fakeSha1' }, undefined); + + assert.strictEqual(editor.getModel().getValue(), origValue); + + session.hunkData.discardAll(); // called when dimissing the session + assert.strictEqual(editor.getModel().getValue(), origValue); + }); + + test('Apply Code\'s preview should be easier to undo/esc #7537', async function () { + model.setValue(`export function fib(n) { + if (n <= 0) return 0; + if (n === 1) return 0; + if (n === 2) return 1; + return fib(n - 1) + fib(n - 2); +}`); + const session = await inlineChatSessionService.createSession(editor, { editMode: EditMode.Live }, CancellationToken.None); + assertType(session); + + await makeEditAsAi([EditOperation.replace(new Range(5, 1, 6, Number.MAX_SAFE_INTEGER), ` + let a = 0, b = 1, c; + for (let i = 3; i <= n; i++) { + c = a + b; + a = b; + b = c; + } + return b; +}`)]); + + assert.strictEqual(session.hunkData.size, 1); + assert.strictEqual(session.hunkData.pending, 1); + assert.ok(session.hunkData.getInfo().every(d => d.getState() === HunkState.Pending)); + + await assertSnapshot(editor.getModel().getValue(), { name: '1' }); + + await model.undo(); + await assertSnapshot(editor.getModel().getValue(), { name: '2' }); + + // overlapping edits (even UNDO) mark edits as accepted + assert.strictEqual(session.hunkData.size, 1); + assert.strictEqual(session.hunkData.pending, 0); + assert.ok(session.hunkData.getInfo().every(d => d.getState() === HunkState.Accepted)); + + // no further change when discarding + session.hunkData.discardAll(); // CANCEL + await assertSnapshot(editor.getModel().getValue(), { name: '2' }); + }); + }); From 4cec36a9d9bdfa4312921e8eb5e6b5c60f82c577 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 28 Aug 2024 15:23:28 +0200 Subject: [PATCH 10/47] colorCustomizations don't get intellisense detail (#226962) --- src/vs/platform/theme/common/colorUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/theme/common/colorUtils.ts b/src/vs/platform/theme/common/colorUtils.ts index 14ceea8847b..a237166f6f6 100644 --- a/src/vs/platform/theme/common/colorUtils.ts +++ b/src/vs/platform/theme/common/colorUtils.ts @@ -159,7 +159,7 @@ class ColorRegistry implements IColorRegistry { public registerColor(id: string, defaults: ColorDefaults | ColorValue | null, description: string, needsTransparency = false, deprecationMessage?: string): ColorIdentifier { const colorContribution: ColorContribution = { id, description, defaults, needsTransparency, deprecationMessage }; this.colorsById[id] = colorContribution; - const propertySchema: IJSONSchemaWithSnippets = { type: 'string', description, format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] }; + const propertySchema: IJSONSchemaWithSnippets = { type: 'string', format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] }; if (deprecationMessage) { propertySchema.deprecationMessage = deprecationMessage; } @@ -168,6 +168,7 @@ class ColorRegistry implements IColorRegistry { propertySchema.patternErrorMessage = nls.localize('transparecyRequired', 'This color must be transparent or it will obscure content'); } this.colorSchema.properties[id] = { + description, oneOf: [ propertySchema, { type: 'string', const: DEFAULT_COLOR_CONFIG_VALUE, description: nls.localize('useDefault', 'Use the default color.') } From bba915f0c5a9fdac253686ab6ff30b5613d0004b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 28 Aug 2024 16:52:44 +0200 Subject: [PATCH 11/47] fix #226732 (#226968) --- .../browser/userDataProfilesEditor.ts | 1 - .../browser/userDataProfilesEditorModel.ts | 13 +++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts index c471d251303..7fa4518bdc5 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts @@ -710,7 +710,6 @@ class ProfileTreeDataSource implements IAsyncDataSource - activateAction.checked = this.userDataProfileService.currentProfile.id === profileElement.profile.id)); + activateAction.enabled = this.userDataProfileService.currentProfile.id !== profileElement.profile.id)); useAsNewWindowProfileAction.checked = profileElement.isNewWindowProfile; disposables.add(profileElement.onDidChange(e => { From dea44122439ea522fa19c30463bca1a5b97fc558 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 28 Aug 2024 17:26:38 +0200 Subject: [PATCH 12/47] disable code lense for the output editor (#226971) fixes https://github.com/microsoft/vscode/issues/226938 --- src/vs/workbench/contrib/output/browser/outputView.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index 9a23e945d85..50d0ab01a9c 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -185,6 +185,7 @@ class OutputEditor extends AbstractTextResourceEditor { options.lineDecorationsWidth = 20; options.rulers = []; options.folding = false; + options.codeLens = false; options.scrollBeyondLastLine = false; options.renderLineHighlight = 'none'; options.minimap = { enabled: false }; From 9eb84caa0db1b4c2690b2cc1880c3eaf18d08018 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 28 Aug 2024 08:27:45 -0700 Subject: [PATCH 13/47] Always show hover hint if explicitly provided Fixes #223368 --- src/vs/editor/browser/services/hoverService/hoverWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/services/hoverService/hoverWidget.ts b/src/vs/editor/browser/services/hoverService/hoverWidget.ts index ed929bc965c..2d8e9ae9392 100644 --- a/src/vs/editor/browser/services/hoverService/hoverWidget.ts +++ b/src/vs/editor/browser/services/hoverService/hoverWidget.ts @@ -222,7 +222,7 @@ export class HoverWidget extends Widget implements IHoverWidget { } // Show the hover hint if needed - if (hideOnHover && options.appearance?.showHoverHint) { + if (options.appearance?.showHoverHint) { const statusBarElement = $('div.hover-row.status-bar'); const infoElement = $('div.info'); infoElement.textContent = localize('hoverhint', 'Hold {0} key to mouse over', isMacintosh ? 'Option' : 'Alt'); From 73aa45be3cd578698c2501d0443d67e3790d6650 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 28 Aug 2024 08:37:54 -0700 Subject: [PATCH 14/47] Don't verify completions are received on Windows Fixes #226760 --- .../browser/terminal.suggest.contribution.ts | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts index 9ef44f9bcea..433de4eedc7 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminal.suggest.contribution.ts @@ -9,6 +9,7 @@ import { AutoOpenBarrier } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { isWindows } from 'vs/base/common/platform'; import { localize2 } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr, IContextKey, IContextKeyService, IReadableSet } from 'vs/platform/contextkey/common/contextkey'; @@ -170,16 +171,20 @@ class TerminalSuggestContribution extends DisposableStore implements ITerminalCo // If completions are requested, pause and queue input events until completions are // received. This fixing some problems in PowerShell, particularly enter not executing - // when typing quickly and some characters being printed twice. - let barrier: AutoOpenBarrier | undefined; - this.add(addon.onDidRequestCompletions(() => { - barrier = new AutoOpenBarrier(2000); - this._instance.pauseInputEvents(barrier); - })); - this.add(addon.onDidReceiveCompletions(() => { - barrier?.open(); - barrier = undefined; - })); + // when typing quickly and some characters being printed twice. On Windows this isn't + // needed because inputs are _not_ echoed when not handled immediately. + // TODO: This should be based on the OS of the pty host, not the client + if (!isWindows) { + let barrier: AutoOpenBarrier | undefined; + this.add(addon.onDidRequestCompletions(() => { + barrier = new AutoOpenBarrier(2000); + this._instance.pauseInputEvents(barrier); + })); + this.add(addon.onDidReceiveCompletions(() => { + barrier?.open(); + barrier = undefined; + })); + } } } } From 2c24fee996dfbd5c7b3207a4be3ebd6b127a73d1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 28 Aug 2024 17:39:31 +0200 Subject: [PATCH 15/47] Polish UI of inline-edits toolbar (#226969) * use lenses for accept/discard commands * use lens actions for headless inline ui (and not right-aligned button bar) * fixes https://github.com/microsoft/vscode/issues/226750 * fixes https://github.com/microsoft/vscode-copilot/issues/7628 * fixes https://github.com/microsoft/vscode-copilot/issues/7707 * fixes (maybe) https://github.com/microsoft/vscode-copilot/issues/7547 * fix https://github.com/microsoft/vscode/issues/226966 * graceful handling of headless run --- .../inlineChat/browser/inlineChatActions.ts | 4 +- .../browser/inlineChatController.ts | 15 ++- .../browser/inlineChatStrategies.ts | 92 +++++++++++++++---- .../contrib/inlineChat/common/inlineChat.ts | 1 + .../browser/view/conflictActions.ts | 4 +- .../browser/view/fixedZoneWidget.ts | 1 + 6 files changed, 96 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 8119cdd08fa..22d6a83fa5d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -11,7 +11,7 @@ import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/em import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_EDIT_MODE, EditMode, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, MENU_INLINE_CHAT_CONTENT_STATUS, ACTION_VIEW_IN_CHAT, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, MENU_INLINE_CHAT_ZONE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_HAS_AGENT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_VISIBLE, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_EDIT_MODE, EditMode, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, MENU_INLINE_CHAT_CONTENT_STATUS, ACTION_VIEW_IN_CHAT, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, MENU_INLINE_CHAT_ZONE, ACTION_DISCARD_CHANGES } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { localize, localize2 } from 'vs/nls'; import { Action2, IAction2Options } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -287,7 +287,7 @@ export class DiscardHunkAction extends AbstractInlineChatAction { constructor() { super({ - id: 'inlineChat.discardHunkChange', + id: ACTION_DISCARD_CHANGES, title: localize('discard', 'Discard'), icon: Codicon.chromeClose, precondition: CTX_INLINE_CHAT_VISIBLE, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 823ea678aa7..18e4a817bfa 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -39,7 +39,7 @@ import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { InlineChatContentWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatContentWidget'; -import { HunkInformation, Session, StashedSession } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; +import { HunkInformation, HunkState, Session, StashedSession } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatError } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl'; import { EditModeStrategy, HunkAction, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies'; import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -989,8 +989,21 @@ export class InlineChatController implements IEditorContribution { // ---- controller API showSaveHint(): void { + if (!this._session) { + return; + } + const status = localize('savehint', "Accept or discard changes to continue saving."); this._ui.value.zone.widget.updateStatus(status, { classes: ['warn'] }); + + if (this._ui.value.zone.position) { + this._editor.revealLineInCenterIfOutsideViewport(this._ui.value.zone.position.lineNumber); + } else { + const hunk = this._session.hunkData.getInfo().find(info => info.getState() === HunkState.Pending); + if (hunk) { + this._editor.revealLineInCenterIfOutsideViewport(hunk.getRangesN()[0].startLineNumber); + } + } } acceptInput() { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index c44f6e6ee7e..28e5b29c7a9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -8,7 +8,7 @@ import { coalesceInPlace } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { themeColorFromId } from 'vs/base/common/themables'; +import { themeColorFromId, ThemeIcon } from 'vs/base/common/themables'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IViewZone, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; import { LineSource, RenderOptions, renderLines } from 'vs/editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines'; @@ -27,7 +27,7 @@ import { SaveReason } from 'vs/workbench/common/editor'; import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { HunkInformation, Session, HunkState } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { InlineChatZoneWidget } from './inlineChatZoneWidget'; -import { CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, InlineChatConfigKeys, MENU_INLINE_CHAT_ZONE, minimapInlineChatDiffInserted, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_DOCUMENT_CHANGED, InlineChatConfigKeys, MENU_INLINE_CHAT_ZONE, minimapInlineChatDiffInserted, overviewRulerInlineChatDiffInserted } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { assertType } from 'vs/base/common/types'; import { IModelService } from 'vs/editor/common/services/model'; import { performAsyncTextEdit, asProgressiveEdit } from './utils'; @@ -43,6 +43,9 @@ import { generateUuid } from 'vs/base/common/uuid'; import { MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Iterable } from 'vs/base/common/iterator'; +import { ConflictActionsFactory, IContentWidgetAction } from 'vs/workbench/contrib/mergeEditor/browser/view/conflictActions'; +import { observableValue } from 'vs/base/common/observable'; +import { IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; export interface IEditObserver { start(): void; @@ -216,8 +219,10 @@ type HunkDisplayData = { decorationIds: string[]; - viewZoneId: string | undefined; - viewZone: IViewZone; + diffViewZoneId: string | undefined; + diffViewZone: IViewZone; + + lensActionsViewZoneIds?: string[]; distance: number; position: Position; @@ -257,6 +262,7 @@ export class LiveStrategy extends EditModeStrategy { private readonly _ctxCurrentChangeShowsDiff: IContextKey; private readonly _progressiveEditingDecorations: IEditorDecorationsCollection; + private readonly _lensActionsFactory: ConflictActionsFactory; private _editCount: number = 0; constructor( @@ -268,6 +274,8 @@ export class LiveStrategy extends EditModeStrategy { @IEditorWorkerService protected readonly _editorWorkerService: IEditorWorkerService, @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IConfigurationService private readonly _configService: IConfigurationService, + @IMenuService private readonly _menuService: IMenuService, + @IContextKeyService private readonly _contextService: IContextKeyService, @ITextFileService textFileService: ITextFileService, @IInstantiationService instaService: IInstantiationService ) { @@ -276,6 +284,7 @@ export class LiveStrategy extends EditModeStrategy { this._ctxCurrentChangeShowsDiff = CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF.bindTo(contextKeyService); this._progressiveEditingDecorations = this._editor.createDecorationsCollection(); + this._lensActionsFactory = this._store.add(new ConflictActionsFactory(this._editor)); } @@ -487,43 +496,93 @@ export class LiveStrategy extends EditModeStrategy { afterLineNumber: -1, heightInLines: result.heightInLines, domNode, - ordinal: 50000 + 1 // more than https://github.com/microsoft/vscode/blob/bf52a5cfb2c75a7327c9adeaefbddc06d529dcad/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts#L42 + ordinal: 50000 + 2 // more than https://github.com/microsoft/vscode/blob/bf52a5cfb2c75a7327c9adeaefbddc06d529dcad/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts#L42 }; const toggleDiff = () => { const scrollState = StableEditorScrollState.capture(this._editor); changeDecorationsAndViewZones(this._editor, (_decorationsAccessor, viewZoneAccessor) => { assertType(data); - if (!data.viewZoneId) { + if (!data.diffViewZoneId) { const [hunkRange] = hunkData.getRangesN(); viewZoneData.afterLineNumber = hunkRange.startLineNumber - 1; - data.viewZoneId = viewZoneAccessor.addZone(viewZoneData); + data.diffViewZoneId = viewZoneAccessor.addZone(viewZoneData); overlay?.updateExtraTop(result.heightInLines); } else { - viewZoneAccessor.removeZone(data.viewZoneId!); + viewZoneAccessor.removeZone(data.diffViewZoneId!); overlay?.updateExtraTop(0); - data.viewZoneId = undefined; + data.diffViewZoneId = undefined; } }); - this._ctxCurrentChangeShowsDiff.set(typeof data?.viewZoneId === 'string'); + this._ctxCurrentChangeShowsDiff.set(typeof data?.diffViewZoneId === 'string'); scrollState.restore(this._editor); }; - const overlay = this._showOverlayToolbar + const overlay = this._showOverlayToolbar && false ? this._instaService.createInstance(InlineChangeOverlay, this._editor, hunkData) : undefined; + + let lensActions: DisposableStore | undefined; + const lensActionsViewZoneIds: string[] = []; + + if (this._showOverlayToolbar && hunkData.getState() === HunkState.Pending) { + + lensActions = new DisposableStore(); + + const menu = this._menuService.createMenu(MENU_INLINE_CHAT_ZONE, this._contextService); + const makeActions = () => { + const actions: IContentWidgetAction[] = []; + const tuples = menu.getActions(); + for (const [, group] of tuples) { + for (const item of group) { + if (item instanceof MenuItemAction) { + + let text = item.label; + + if (item.id === ACTION_TOGGLE_DIFF) { + text = item.checked ? 'Hide Changes' : 'Show Changes'; + } else if (ThemeIcon.isThemeIcon(item.item.icon)) { + text = `$(${item.item.icon.id}) ${text}`; + } + + actions.push({ + text, + tooltip: item.tooltip, + action: async () => item.run(), + }); + } + } + } + return actions; + }; + + const obs = observableValue(this, makeActions()); + lensActions.add(menu.onDidChange(() => obs.set(makeActions(), undefined))); + lensActions.add(menu); + + lensActions.add(this._lensActionsFactory.createWidget(viewZoneAccessor, + hunkRanges[0].startLineNumber - 1, + obs, + lensActionsViewZoneIds + )); + } + const remove = () => { changeDecorationsAndViewZones(this._editor, (decorationsAccessor, viewZoneAccessor) => { assertType(data); for (const decorationId of data.decorationIds) { decorationsAccessor.removeDecoration(decorationId); } - if (data.viewZoneId) { - viewZoneAccessor.removeZone(data.viewZoneId); + if (data.diffViewZoneId) { + viewZoneAccessor.removeZone(data.diffViewZoneId); } data.decorationIds = []; - data.viewZoneId = undefined; + data.diffViewZoneId = undefined; + + data.lensActionsViewZoneIds?.forEach(viewZoneAccessor.removeZone); + data.lensActionsViewZoneIds = undefined; + lensActions?.dispose(); }); overlay?.dispose(); @@ -548,8 +607,9 @@ export class LiveStrategy extends EditModeStrategy { data = { hunk: hunkData, decorationIds, - viewZoneId: '', - viewZone: viewZoneData, + diffViewZoneId: '', + diffViewZone: viewZoneData, + lensActionsViewZoneIds, distance: myDistance, position: hunkRanges[0].getStartPosition().delta(-1), acceptHunk, diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index d656e6c3f25..f4c87af0d22 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -116,6 +116,7 @@ export const CTX_INLINE_CHAT_RESPONSE_TYPE = new RawContextKey { this.widgetDomNode.style.height = `${height}px`; }, From ee83a4c90e6cf084d67c16ec495b1735e2cd6f3c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 28 Aug 2024 08:43:19 -0700 Subject: [PATCH 16/47] Use OS separator when injecting ../ completion This should fix the issue on Windows, it was working fine on macOS. Fixes #222237 --- .../contrib/terminal/browser/media/shellIntegration.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 index c624a5f0036..e3a7a149174 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 @@ -388,9 +388,9 @@ function Send-Completions { if ($completions.CompletionMatches.Count -gt 0 -and $completions.CompletionMatches.Where({ $_.ResultType -eq 3 -or $_.ResultType -eq 4 })) { # Add `../ relative to the top completion $firstCompletion = $completions.CompletionMatches[0] - if ($firstCompletion.CompletionText.StartsWith('../')) { - if ($completionPrefix -match '(\.\.\/)+') { - $parentDir = "$($matches[0])../" + if ($firstCompletion.CompletionText.StartsWith("..$([System.IO.Path]::DirectorySeparatorChar)")) { + if ($completionPrefix -match "(\.\.$([System.IO.Path]::DirectorySeparatorChar))+") { + $parentDir = "$($matches[0])..$([System.IO.Path]::DirectorySeparatorChar)" $currentPath = Split-Path -Parent $firstCompletion.ToolTip try { $parentDirPath = Split-Path -Parent $currentPath From e81a3a0581f3e047fb1223392261060c8d6ee3e1 Mon Sep 17 00:00:00 2001 From: Ulugbek Abdullaev Date: Wed, 28 Aug 2024 18:22:33 +0200 Subject: [PATCH 17/47] rename widget: remove trace log to avoid flooding until we unmount the widget from editor correctly (#226977) * rename widget: remove trace log to avoid flooding until we unmount the widget from editor correctly * add a fixme & link corresponding issue --- src/vs/editor/contrib/rename/browser/renameWidget.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/rename/browser/renameWidget.ts b/src/vs/editor/contrib/rename/browser/renameWidget.ts index b284cd519fd..1f6fcc14106 100644 --- a/src/vs/editor/contrib/rename/browser/renameWidget.ts +++ b/src/vs/editor/contrib/rename/browser/renameWidget.ts @@ -318,7 +318,8 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable } afterRender(position: ContentWidgetPositionPreference | null): void { - this._trace('invoking afterRender, position: ', position ? 'not null' : 'null'); + // FIXME@ulugbekna: commenting trace log out until we start unmounting the widget from editor properly - https://github.com/microsoft/vscode/issues/226975 + // this._trace('invoking afterRender, position: ', position ? 'not null' : 'null'); if (position === null) { // cancel rename when input widget isn't rendered anymore this.cancelInput(true, 'afterRender (because position is null)'); @@ -363,7 +364,7 @@ export class RenameWidget implements IRenameWidget, IContentWidget, IDisposable } cancelInput(focusEditor: boolean, caller: string): void { - this._trace(`invoking cancelInput, caller: ${caller}, _currentCancelInput: ${this._currentAcceptInput ? 'not undefined' : 'undefined'}`); + // this._trace(`invoking cancelInput, caller: ${caller}, _currentCancelInput: ${this._currentAcceptInput ? 'not undefined' : 'undefined'}`); this._currentCancelInput?.(focusEditor); } From 368c17e0732488da315d39be4072df2515f8806c Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 28 Aug 2024 18:59:38 +0200 Subject: [PATCH 18/47] profiles editor feedback (#226982) --- .../browser/userDataProfilesEditorModel.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts index 9431ddf166c..42afc9e189d 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts @@ -771,7 +771,7 @@ export class UserDataProfilesEditorModel extends EditorModel { const activateAction = disposables.add(new Action( 'userDataProfile.activate', - localize('active', "Use for Current Window"), + localize('active', "Use this Profile for Current Window"), ThemeIcon.asClassName(Codicon.check), true, () => this.userDataProfileManagementService.switchProfile(profileElement.profile) @@ -808,20 +808,10 @@ export class UserDataProfilesEditorModel extends EditorModel { () => this.openWindow(profileElement.profile) )); - const useAsNewWindowProfileAction = disposables.add(new Action( - 'userDataProfile.useAsNewWindowProfile', - localize('use as new window', "Use for New Windows"), - undefined, - true, - () => profileElement.toggleNewWindowProfile() - )); - const primaryActions: IAction[] = []; primaryActions.push(activateAction); primaryActions.push(newWindowAction); const secondaryActions: IAction[] = []; - secondaryActions.push(useAsNewWindowProfileAction); - secondaryActions.push(new Separator()); secondaryActions.push(copyFromProfileAction); secondaryActions.push(exportAction); if (!profile.isDefault) { @@ -839,13 +829,6 @@ export class UserDataProfilesEditorModel extends EditorModel { disposables.add(this.userDataProfileService.onDidChangeCurrentProfile(() => activateAction.enabled = this.userDataProfileService.currentProfile.id !== profileElement.profile.id)); - useAsNewWindowProfileAction.checked = profileElement.isNewWindowProfile; - disposables.add(profileElement.onDidChange(e => { - if (e.newWindowProfile) { - useAsNewWindowProfileAction.checked = profileElement.isNewWindowProfile; - } - })); - return [profileElement, disposables]; } From 2e4a043ef387856c67ac934c36ca67073f56624d Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 28 Aug 2024 10:12:41 -0700 Subject: [PATCH 19/47] debug: bump js-debug to 1.93 (#226986) --- product.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/product.json b/product.json index 1803992a004..908be250245 100644 --- a/product.json +++ b/product.json @@ -50,8 +50,8 @@ }, { "name": "ms-vscode.js-debug", - "version": "1.92.0", - "sha256": "e5d0a74728292423631f79d076ecb2bc129f9637bcbc2529e48a0fd53baa69cc", + "version": "1.93.0", + "sha256": "9339cb8e6b77f554df54d79e71f533279cb76b0f9b04c207f633bfd507442b6a", "repo": "https://github.com/microsoft/vscode-js-debug", "metadata": { "id": "25629058-ddac-4e17-abba-74678e126c5d", From 51e4430ffe500ee25c83c8e38a99841fa04dd78c Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 28 Aug 2024 10:55:21 -0700 Subject: [PATCH 20/47] testing: call stack tpi refinements (#226981) Fixes #226855 - ctrl+click on words in files takes you to the source Fixes #226863 - clicking file titles should toggle collapse Fixes #226857 - add 'collapse all' button in peek Fixes #226852 - name 'go to source' actions better --- .../contrib/debug/browser/callStackWidget.ts | 124 ++++++++++++++---- .../debug/browser/media/callStackWidget.css | 6 + .../testResultsView/testMessageStack.ts | 4 + .../testResultsView/testResultsSubject.ts | 3 + .../testResultsView/testResultsTree.ts | 13 +- .../testResultsView/testResultsViewContent.ts | 7 + .../testing/browser/testing.contribution.ts | 3 +- .../testing/browser/testingOutputPeek.ts | 64 ++++++++- .../testing/common/testingContextKeys.ts | 1 + 9 files changed, 188 insertions(+), 37 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/callStackWidget.ts b/src/vs/workbench/contrib/debug/browser/callStackWidget.ts index 50cfbf0f3a5..0b6b0e6bc28 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackWidget.ts @@ -12,20 +12,24 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { autorun, autorunWithStore, derived, IObservable, ISettableObservable, observableValue } from 'vs/base/common/observable'; +import { autorun, autorunWithStore, derived, IObservable, ISettableObservable, observableValue, transaction } from 'vs/base/common/observable'; import { ThemeIcon } from 'vs/base/common/themables'; import { Constants } from 'vs/base/common/uint'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import 'vs/css!./media/callStackWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { EditorContributionCtor, EditorContributionInstantiation, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/codeEditorWidget'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { IWordAtPosition } from 'vs/editor/common/core/wordHelper'; +import { IEditorContribution, IEditorDecorationsCollection } from 'vs/editor/common/editorCommon'; import { Location } from 'vs/editor/common/languages'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ClickLinkGesture, ClickLinkMouseEvent } from 'vs/editor/contrib/gotoSymbol/browser/link/clickLinkGesture'; import { localize, localize2 } from 'vs/nls'; import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; @@ -38,7 +42,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { ResourceLabel } from 'vs/workbench/browser/labels'; import { makeStackFrameColumnDecoration, TOP_STACK_FRAME_DECORATION } from 'vs/workbench/contrib/debug/browser/callStackEditorContribution'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; export class CallStackFrame { @@ -97,6 +101,9 @@ class WrappedCustomStackFrame implements IFrameLikeItem { constructor(public readonly original: CustomStackFrame) { } } +const isFrameLike = (item: unknown): item is IFrameLikeItem => + item instanceof WrappedCallStackFrame || item instanceof WrappedCustomStackFrame; + type ListItem = WrappedCallStackFrame | SkippedCallFrames | WrappedCustomStackFrame; const WIDGET_CLASS_NAME = 'multiCallStackWidget'; @@ -157,6 +164,17 @@ export class CallStackWidget extends Disposable { this.layoutEmitter.fire(); } + public collapseAll() { + transaction(tx => { + for (let i = 0; i < this.list.length; i++) { + const frame = this.list.element(i); + if (isFrameLike(frame)) { + frame.collapsed.set(true, tx); + } + } + }); + } + private async loadFrame(replacing: SkippedCallFrames): Promise { if (!this.cts) { return; @@ -356,9 +374,9 @@ abstract class AbstractFrameRenderer { - item.collapsed.set(!item.collapsed.get(), undefined); - })); + const toggleCollapse = () => item.collapsed.set(!item.collapsed.get(), undefined); + elementStore.add(collapse.onDidClick(toggleCollapse)); + elementStore.add(dom.addDisposableListener(elements.title, 'click', toggleCollapse)); } disposeElement(element: ListItem, index: number, templateData: T, height: number | undefined): void { @@ -382,26 +400,33 @@ class FrameCodeRenderer extends AbstractFrameRenderer { private readonly containingEditor: ICodeEditor | undefined, private readonly onLayout: Event, @ITextModelService private readonly modelService: ITextModelService, - @ICodeEditorService private readonly editorService: ICodeEditorService, @IInstantiationService instantiationService: IInstantiationService, ) { super(instantiationService); } protected override finishRenderTemplate(data: IAbstractFrameRendererTemplateData): IStackTemplateData { + // override default e.g. language contributions, only allow users to click + // on code in the call stack to go to its source location + const contributions: IEditorContributionDescription[] = [{ + id: ClickToLocationContribution.ID, + instantiation: EditorContributionInstantiation.BeforeFirstInteraction, + ctor: ClickToLocationContribution as EditorContributionCtor, + }]; + const editor = this.containingEditor ? this.instantiationService.createInstance( EmbeddedCodeEditorWidget, data.elements.editor, editorOptions, - { isSimpleWidget: true }, + { isSimpleWidget: true, contributions }, this.containingEditor, ) : this.instantiationService.createInstance( CodeEditorWidget, data.elements.editor, editorOptions, - { isSimpleWidget: true }, + { isSimpleWidget: true, contributions }, ); data.templateStore.add(editor); @@ -423,20 +448,6 @@ class FrameCodeRenderer extends AbstractFrameRenderer { const uri = item.source!; template.label.element.setFile(uri); - template.elements.title.role = 'link'; - elementStore.add(dom.addDisposableListener(template.elements.title, 'click', e => { - this.editorService.openCodeEditor({ - resource: uri, - options: { - selection: Range.fromPositions({ - column: item.column ?? 1, - lineNumber: item.line ?? 1, - }), - selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, - }, - }, this.containingEditor || null, e.ctrlKey || e.metaKey); - })); - const cts = new CancellationTokenSource(); elementStore.add(toDisposable(() => cts.dispose(true))); this.modelService.createModelReference(uri).then(reference => { @@ -632,6 +643,73 @@ class SkippedRenderer implements IListRenderer { } } +/** A simple contribution that makes all data in the editor clickable to go to the location */ +class ClickToLocationContribution extends Disposable implements IEditorContribution { + public static readonly ID = 'clickToLocation'; + private readonly linkDecorations: IEditorDecorationsCollection; + private current: { line: number; word: IWordAtPosition } | undefined; + + constructor( + private readonly editor: ICodeEditor, + @IEditorService editorService: IEditorService, + ) { + super(); + this.linkDecorations = editor.createDecorationsCollection(); + this._register(toDisposable(() => this.linkDecorations.clear())); + + const clickLinkGesture = this._register(new ClickLinkGesture(editor)); + + this._register(clickLinkGesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, keyboardEvent]) => { + this.onMove(mouseEvent); + })); + this._register(clickLinkGesture.onExecute((e) => { + const model = this.editor.getModel(); + if (!this.current || !model) { + return; + } + + editorService.openEditor({ + resource: model.uri, + options: { + selection: Range.fromPositions(new Position(this.current.line, this.current.word.startColumn)), + selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport, + }, + }, e.hasSideBySideModifier ? SIDE_GROUP : undefined); + })); + } + + private onMove(mouseEvent: ClickLinkMouseEvent) { + if (!mouseEvent.hasTriggerModifier) { + return this.clear(); + } + + const position = mouseEvent.target.position; + const word = position && this.editor.getModel()?.getWordAtPosition(position); + if (!word) { + return this.clear(); + } + + const prev = this.current?.word; + if (prev && prev.startColumn === word.startColumn && prev.endColumn === word.endColumn && prev.word === word.word) { + return; + } + + this.current = { word, line: position.lineNumber }; + this.linkDecorations.set([{ + range: new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn), + options: { + description: 'call-stack-go-to-file-link', + inlineClassName: 'call-stack-go-to-file-link', + }, + }]); + } + + private clear() { + this.linkDecorations.clear(); + this.current = undefined; + } +} + registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css b/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css index 60f5493087f..e4c0a1a61b1 100644 --- a/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/callStackWidget.css @@ -59,3 +59,9 @@ line-height: inherit !important; } } + +.monaco-editor .call-stack-go-to-file-link { + text-decoration: underline; + cursor: pointer; + color: var(--vscode-editorLink-activeForeground) !important; +} diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts index cb133b4b20d..c6c396e5840 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testMessageStack.ts @@ -30,6 +30,10 @@ export class TestResultStackWidget extends Disposable { )); } + public collapseAll() { + this.widget.collapseAll(); + } + public update(messageFrame: AnyStackFrame, stack: ITestMessageStackFrame[]) { this.widget.setFrames([messageFrame, ...stack.map(frame => new CallStackFrame( frame.label, diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject.ts index 9fa863304ff..e61463bcdc0 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject.ts @@ -22,6 +22,9 @@ interface ISubjectCommon { controllerId: string; } +export const inspectSubjectHasStack = (subject: InspectSubject | undefined) => + subject instanceof MessageSubject && !!subject.stack?.length; + export class MessageSubject implements ISubjectCommon { public readonly test: ITestItem; public readonly message: ITestMessage; diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts index a65a1057df5..6be1cd3c43f 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts @@ -838,22 +838,21 @@ class TreeActionsProvider { } if (element instanceof TestMessageElement) { + id = MenuId.TestMessageContext; + contextKeys.push([TestingContextKeys.testMessageContext.key, element.contextValue]); + primary.push(new Action( - 'testing.outputPeek.goToFile', - localize('testing.goToFile', "Go to Source"), + 'testing.outputPeek.goToTest', + localize('testing.goToTest', "Go to Test"), ThemeIcon.asClassName(Codicon.goToFile), undefined, () => this.commandService.executeCommand('vscode.revealTest', element.test.item.extId), )); - } - if (element instanceof TestMessageElement) { - id = MenuId.TestMessageContext; - contextKeys.push([TestingContextKeys.testMessageContext.key, element.contextValue]); if (this.showRevealLocationOnMessages && element.location) { primary.push(new Action( 'testing.outputPeek.goToError', - localize('testing.goToError', "Go to Source"), + localize('testing.goToError', "Go to Error"), ThemeIcon.asClassName(Codicon.goToFile), undefined, () => this.editorService.openEditor({ diff --git a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts index db80e2c6ed8..67f9f05bfc7 100644 --- a/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts +++ b/src/vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent.ts @@ -322,6 +322,13 @@ export class TestResultsViewContent extends Disposable { }); } + /** + * Collapses all displayed stack frames. + */ + public collapseStack() { + this.callStackWidget.collapseAll(); + } + private getCallFrames(subject: InspectSubject) { if (!(subject instanceof MessageSubject)) { return undefined; diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts index e422256e519..8caac4a2527 100644 --- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -25,7 +25,7 @@ import { testingResultsIcon, testingViewIcon } from 'vs/workbench/contrib/testin import { TestCoverageView } from 'vs/workbench/contrib/testing/browser/testCoverageView'; import { TestingDecorationService, TestingDecorations } from 'vs/workbench/contrib/testing/browser/testingDecorations'; import { TestingExplorerView } from 'vs/workbench/contrib/testing/browser/testingExplorerView'; -import { CloseTestPeek, GoToNextMessageAction, GoToPreviousMessageAction, OpenMessageInEditorAction, TestResultsView, TestingOutputPeekController, TestingPeekOpener, ToggleTestingPeekHistory } from 'vs/workbench/contrib/testing/browser/testingOutputPeek'; +import { CloseTestPeek, CollapsePeekStack, GoToNextMessageAction, GoToPreviousMessageAction, OpenMessageInEditorAction, TestResultsView, TestingOutputPeekController, TestingPeekOpener, ToggleTestingPeekHistory } from 'vs/workbench/contrib/testing/browser/testingOutputPeek'; import { TestingProgressTrigger } from 'vs/workbench/contrib/testing/browser/testingProgressUiService'; import { TestingViewPaneContainer } from 'vs/workbench/contrib/testing/browser/testingViewPaneContainer'; import { testingConfiguration } from 'vs/workbench/contrib/testing/common/configuration'; @@ -136,6 +136,7 @@ registerAction2(GoToPreviousMessageAction); registerAction2(GoToNextMessageAction); registerAction2(CloseTestPeek); registerAction2(ToggleTestingPeekHistory); +registerAction2(CollapsePeekStack); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingContentProvider, LifecyclePhase.Restored); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TestingPeekOpener, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 2bac0cf08ee..3acce2ece81 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -14,6 +14,7 @@ import { Iterable } from 'vs/base/common/iterator'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { observableValue } from 'vs/base/common/observable'; import { count } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -43,6 +44,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { bindContextKey } from 'vs/platform/observable/common/platformObservableUtils'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -52,7 +54,7 @@ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity' import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewDescriptorService } from 'vs/workbench/common/views'; import { renderTestMessageAsText } from 'vs/workbench/contrib/testing/browser/testMessageColorizer'; -import { InspectSubject, MessageSubject, TaskSubject, TestOutputSubject, mapFindTestMessage } from 'vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject'; +import { InspectSubject, MessageSubject, TaskSubject, TestOutputSubject, inspectSubjectHasStack, mapFindTestMessage } from 'vs/workbench/contrib/testing/browser/testResultsView/testResultsSubject'; import { TestResultsViewContent } from 'vs/workbench/contrib/testing/browser/testResultsView/testResultsViewContent'; import { testingMessagePeekBorder, testingPeekBorder, testingPeekHeaderBackground, testingPeekMessageHeaderBackground } from 'vs/workbench/contrib/testing/browser/theme'; import { AutoOpenPeekViewWhen, TestingConfigKeys, getTestingConfiguration } from 'vs/workbench/contrib/testing/common/configuration'; @@ -498,6 +500,13 @@ export class TestingOutputPeekController extends Disposable implements IEditorCo this.peek.clear(); } + /** + * Collapses all displayed stack frames. + */ + public collapseStack() { + this.peek.value?.collapseStack(); + } + /** * Shows the next message in the peek, if possible. */ @@ -645,10 +654,14 @@ class TestResultsPeek extends PeekViewWidget { private static lastHeightInLines?: number; private readonly visibilityChange = this._disposables.add(new Emitter()); + private readonly _current = observableValue('testPeekCurrent', undefined); private content!: TestResultsViewContent; private scopedContextKeyService!: IContextKeyService; private dimension?: dom.Dimension; - public current?: InspectSubject; + + public get current() { + return this._current.get(); + } constructor( editor: ICodeEditor, @@ -702,7 +715,14 @@ class TestResultsPeek extends PeekViewWidget { protected override _fillHead(container: HTMLElement): void { super._fillHead(container); - const menu = this.menuService.createMenu(MenuId.TestPeekTitle, this.contextKeyService); + const menuContextKeyService = this._disposables.add(this.contextKeyService.createScoped(container)); + this._disposables.add(bindContextKey( + TestingContextKeys.peekHasStack, + menuContextKeyService, + reader => inspectSubjectHasStack(this._current.read(reader)), + )); + + const menu = this.menuService.createMenu(MenuId.TestPeekTitle, menuContextKeyService); const actionBar = this._actionbarWidget!; this._disposables.add(menu.onDidChange(() => { actions.length = 0; @@ -732,7 +752,7 @@ class TestResultsPeek extends PeekViewWidget { */ public setModel(subject: InspectSubject): Promise { if (subject instanceof TaskSubject || subject instanceof TestOutputSubject) { - this.current = subject; + this._current.set(subject, undefined); return this.showInPlace(subject); } @@ -743,14 +763,14 @@ class TestResultsPeek extends PeekViewWidget { return Promise.resolve(); } - this.current = subject; + this._current.set(subject, undefined); if (!revealLocation) { return this.showInPlace(subject); } // If there is a stack we want to display, ensure the default size is large-ish const peekLines = TestResultsPeek.lastHeightInLines || Math.max( - subject instanceof MessageSubject && subject.stack?.length ? Math.ceil(this.getVisibleEditorLines() / 2) : 0, + inspectSubjectHasStack(subject) ? Math.ceil(this.getVisibleEditorLines() / 2) : 0, hintMessagePeekHeight(message) ); @@ -760,6 +780,13 @@ class TestResultsPeek extends PeekViewWidget { return this.showInPlace(subject); } + /** + * Collapses all displayed stack frames. + */ + public collapseStack() { + this.content.collapseStack(); + } + private getVisibleEditorLines() { // note that we don't use the view ranges because we don't want to get // thrown off by large wrapping lines. Being approximate here is okay. @@ -1025,6 +1052,31 @@ export class GoToPreviousMessageAction extends Action2 { } } +export class CollapsePeekStack extends Action2 { + public static readonly ID = 'testing.collapsePeekStack'; + constructor() { + super({ + id: CollapsePeekStack.ID, + title: localize2('testing.collapsePeekStack', 'Collapse Stack Frames'), + icon: Codicon.collapseAll, + category: Categories.Test, + menu: [{ + id: MenuId.TestPeekTitle, + when: TestingContextKeys.peekHasStack, + group: 'navigation', + order: 4, + }], + }); + } + + public override run(accessor: ServicesAccessor) { + const editor = getPeekedEditorFromFocus(accessor.get(ICodeEditorService)); + if (editor) { + TestingOutputPeekController.get(editor)?.collapseStack(); + } + } +} + export class OpenMessageInEditorAction extends Action2 { public static readonly ID = 'testing.openMessageInEditor'; constructor() { diff --git a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts index ac303b4886f..1cfd764dc23 100644 --- a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts +++ b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts @@ -29,6 +29,7 @@ export namespace TestingContextKeys { export const inlineCoverageEnabled = new RawContextKey('testing.inlineCoverageEnabled', false, { type: 'boolean', description: localize('testing.inlineCoverageEnabled', 'Indicates whether inline coverage is shown') }); export const canGoToRelatedCode = new RawContextKey('testing.canGoToRelatedCode', false, { type: 'boolean', description: localize('testing.canGoToRelatedCode', 'Whether a controller implements a capability to find code related to a test') }); export const canGoToRelatedTest = new RawContextKey('testing.canGoToRelatedTest', false, { type: 'boolean', description: localize('testing.canGoToRelatedTest', 'Whether a controller implements a capability to find tests related to code') }); + export const peekHasStack = new RawContextKey('testing.peekHasStack', false, { type: 'boolean', description: localize('testing.peekHasStack', 'Whether the message shown in a peek view has a stack trace') }); export const capabilityToContextKey: { [K in TestRunProfileBitset]: RawContextKey } = { [TestRunProfileBitset.Run]: hasRunnableTests, From d03aeaf1fb34084d626056f49088c95cbabc17e0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 28 Aug 2024 20:50:13 +0200 Subject: [PATCH 21/47] Can't open files (#226898) (#226996) --- build/.moduleignore | 2 +- build/.webignore | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/.moduleignore b/build/.moduleignore index 97b504d8522..3f573e06078 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -163,7 +163,7 @@ typescript/lib/tsserverlibrary.js jschardet/index.js jschardet/src/** -jschardet/dist/jschardet.js +# TODO@esm uncomment when we can use jschardet.min.js again jschardet/dist/jschardet.js es6-promise/lib/** diff --git a/build/.webignore b/build/.webignore index d42f9775ba9..837366b67f7 100644 --- a/build/.webignore +++ b/build/.webignore @@ -14,7 +14,7 @@ jschardet/index.js jschardet/src/** -jschardet/dist/jschardet.js +# TODO@esm uncomment when we can use jschardet.min.js again jschardet/dist/jschardet.js vscode-textmate/webpack.config.js From be0d1775008364fe1fb74c7f7a1f37e9fb6a9950 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:12:07 -0700 Subject: [PATCH 22/47] fix desc for session (#226900) --- src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts b/src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts index d31f5db77f0..efc6d52dca5 100644 --- a/src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts +++ b/src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts @@ -54,7 +54,8 @@ declare module 'vscode' { /** * An object with a lifespan that matches the session's lifespan. If the provider chooses to, this object can be used as the key for a cache, - * and searches with the same session object can search the same cache. When the token is cancelled, the session is complete and the cache can be cleared. + * and searches with the same session object can search the same cache. When the object is garbage-collected, the session is complete and the cache can be cleared. + * Please do not store any references to the session object, except via a weak reference (e.g. `WeakRef` or `WeakMap`). */ session: unknown; From b272c80fc7958a43279fd8d9c410bfe20217cc1d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:14:41 -0700 Subject: [PATCH 23/47] Fix api docs referencing old name Fixes #226749 --- src/vscode-dts/vscode.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 09ce5a92296..b84653d1505 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -7505,7 +7505,7 @@ declare module 'vscode' { * @example * // Execute a command in a terminal immediately after being created * const myTerm = window.createTerminal(); - * window.onDidActivateTerminalShellIntegration(async ({ terminal, shellIntegration }) => { + * window.onDidChangeTerminalShellIntegration(async ({ terminal, shellIntegration }) => { * if (terminal === myTerm) { * const command = shellIntegration.executeCommand({ * command: 'echo', From 478b03625d8122f76a5963ce6df49a93e5ddf710 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 28 Aug 2024 12:23:04 -0700 Subject: [PATCH 24/47] don't duplicate walkthrough steps, add `when` clause (#226983) fix #226841 --- .../common/gettingStartedContent.ts | 87 +------------------ 1 file changed, 2 insertions(+), 85 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts index 0b826034b3b..3ca45752928 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/common/gettingStartedContent.ts @@ -226,6 +226,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ 'onSettingChanged:workbench.colorTheme', 'onCommand:workbench.action.selectTheme' ], + when: '!accessibilityModeEnabled', media: { type: 'markdown', path: 'theme_picker', } }, { @@ -399,7 +400,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ isFeatured: true, icon: setupIcon, when: CONTEXT_ACCESSIBILITY_MODE_ENABLED.key, - next: 'SetupScreenReaderExtended', + next: 'Setup', content: { type: 'steps', steps: [ @@ -470,90 +471,6 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ ] } }, - { - id: 'SetupScreenReaderExtended', - title: localize('gettingStarted.setupScreenReaderExtended.title', "Learn more about using VS Code with a Screen Reader"), - description: localize('gettingStarted.setupScreenReaderExtended.description', "Customize your editor, learn the basics, and start coding"), - isFeatured: true, - icon: setupIcon, - when: `!isWeb && ${CONTEXT_ACCESSIBILITY_MODE_ENABLED.key}`, - content: { - type: 'steps', - steps: [ - { - id: 'extensionsWeb', - title: localize('gettingStarted.extensions.title', "Code with extensions"), - description: localize('gettingStarted.extensionsWeb.description.interpolated', "Extensions are VS Code's power-ups. A growing number are becoming available in the web.\n{0}", Button(localize('browsePopularWeb', "Browse Popular Web Extensions"), 'command:workbench.extensions.action.showPopularExtensions')), - when: 'workspacePlatform == \'webworker\'', - media: { - type: 'markdown', path: 'empty' - } - }, - { - id: 'findLanguageExtensions', - title: localize('gettingStarted.findLanguageExts.title', "Rich support for all your languages"), - description: localize('gettingStarted.findLanguageExts.description.interpolated', "Code smarter with syntax highlighting, code completion, linting and debugging. While many languages are built-in, many more can be added as extensions.\n{0}", Button(localize('browseLangExts', "Browse Language Extensions"), 'command:workbench.extensions.action.showLanguageExtensions')), - when: 'workspacePlatform != \'webworker\'', - media: { - type: 'markdown', path: 'empty' - } - }, - { - id: 'settings', - title: localize('gettingStarted.settings.title', "Tune your settings"), - description: localize('gettingStarted.settings.description.interpolated', "Customize every aspect of VS Code and your extensions to your liking. Commonly used settings are listed first to get you started.\n{0}", Button(localize('tweakSettings', "Open Settings"), 'command:toSide:workbench.action.openSettings')), - media: { - type: 'markdown', path: 'empty' - } - }, - { - id: 'settingsSync', - title: localize('gettingStarted.settingsSync.title', "Sync settings across devices"), - description: localize('gettingStarted.settingsSync.description.interpolated', "Keep your essential customizations backed up and updated across all your devices.\n{0}", Button(localize('enableSync', "Backup and Sync Settings"), 'command:workbench.userDataSync.actions.turnOn')), - when: 'syncStatus != uninitialized', - completionEvents: ['onEvent:sync-enabled'], - media: { - type: 'markdown', path: 'empty' - } - }, - { - id: 'commandPaletteTask', - title: localize('gettingStarted.commandPalette.title', "Unlock productivity with the Command Palette "), - description: localize('gettingStarted.commandPalette.description.interpolated', "Run commands without reaching for your mouse to accomplish any task in VS Code.\n{0}", Button(localize('commandPalette', "Open Command Palette"), 'command:workbench.action.showCommands')), - media: { - type: 'markdown', path: 'empty' - } - }, - { - id: 'pickAFolderTask-Mac', - title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), - description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFileFolder')), - when: 'isMac && workspaceFolderCount == 0', - media: { - type: 'markdown', path: 'empty' - } - }, - { - id: 'pickAFolderTask-Other', - title: localize('gettingStarted.setup.OpenFolder.title', "Open up your code"), - description: localize('gettingStarted.setup.OpenFolder.description.interpolated', "You're all set to start coding. Open a project folder to get your files into VS Code.\n{0}", Button(localize('pickFolder', "Pick a Folder"), 'command:workbench.action.files.openFolder')), - when: '!isMac && workspaceFolderCount == 0', - media: { - type: 'markdown', path: 'empty' - } - }, - { - id: 'quickOpen', - title: localize('gettingStarted.quickOpen.title', "Quickly navigate between your files"), - description: localize('gettingStarted.quickOpen.description.interpolated', "Navigate between files in an instant with one keystroke. Tip: Open multiple files by pressing the right arrow key.\n{0}", Button(localize('quickOpen', "Quick Open a File"), 'command:toSide:workbench.action.quickOpen')), - when: 'workspaceFolderCount != 0', - media: { - type: 'markdown', path: 'empty' - } - }, - ] - } - }, { id: 'Beginner', isFeatured: false, From fbe0d9b4148a457a7b479ffa3527973d307213fa Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:40:44 -0700 Subject: [PATCH 25/47] Cancel right click action when on scroll slider Fixes #223358 --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index f38b9bf80b5..ff8607f6019 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -2305,7 +2305,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { async handleMouseEvent(event: MouseEvent, contextMenu: IMenu): Promise<{ cancelContextMenu: boolean } | void> { // Don't handle mouse event if it was on the scroll bar - if (dom.isHTMLElement(event.target) && event.target.classList.contains('scrollbar')) { + if (dom.isHTMLElement(event.target) && (event.target.classList.contains('scrollbar') || event.target.classList.contains('slider'))) { return { cancelContextMenu: true }; } From c5244860633ac41d40bb439c4df8030552db9559 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 28 Aug 2024 13:29:22 -0700 Subject: [PATCH 26/47] testing: fix alternate actions being inappropriately sticky (#227002) * testing: fix alternate actions being inappropriately sticky We should listen on the window, not just the editor, for alt key events. * fix compile --- .../testing/browser/testingDecorations.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index 7884f77acb1..0ff1cc43644 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/actions'; import { equals } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -456,11 +457,24 @@ export class TestingDecorations extends Disposable implements IEditorContributio decorations.syncDecorations(this._currentUri); } })); - this._register(this.editor.onKeyDown(e => { - if (e.keyCode === KeyCode.Alt && this._currentUri) { - decorations.updateDecorationsAlternateAction(this._currentUri!, true); + + const win = dom.getWindow(editor.getDomNode()); + this._register(dom.addDisposableListener(win, 'keydown', e => { + if (new StandardKeyboardEvent(e).keyCode === KeyCode.Alt && this._currentUri) { + decorations.updateDecorationsAlternateAction(this._currentUri, true); + } + })); + this._register(dom.addDisposableListener(win, 'keyup', e => { + if (new StandardKeyboardEvent(e).keyCode === KeyCode.Alt && this._currentUri) { + decorations.updateDecorationsAlternateAction(this._currentUri, false); } })); + this._register(dom.addDisposableListener(win, 'blur', () => { + if (this._currentUri) { + decorations.updateDecorationsAlternateAction(this._currentUri, false); + } + })); + this._register(this.editor.onKeyUp(e => { if (e.keyCode === KeyCode.Alt && this._currentUri) { decorations.updateDecorationsAlternateAction(this._currentUri!, false); From 478d1bf1545a69eac893f07de11978e634af5e0e Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 28 Aug 2024 22:33:25 +0200 Subject: [PATCH 27/47] Sorting should have the default state checked (#227007) and improve label for sort-by-position-in-file Fixes #226764 Fixes #226723 --- .../contrib/comments/browser/commentsView.ts | 2 +- .../comments/browser/commentsViewActions.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts index 253f7023f72..75deafec5d3 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsView.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts @@ -172,7 +172,7 @@ export class CommentsPanel extends FilterViewPane implements ICommentsView { this.filters = this._register(new CommentsFilters({ showResolved: this.viewState['showResolved'] !== false, showUnresolved: this.viewState['showUnresolved'] !== false, - sortBy: this.viewState['sortBy'], + sortBy: this.viewState['sortBy'] ?? CommentsSortOrder.ResourceAscending, }, this.contextKeyService)); this.filter = new Filter(new FilterOptions(this.filterWidget.getFilterText(), this.filters.showResolved, this.filters.showUnresolved)); diff --git a/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts b/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts index 8fedca8e845..e2494534a05 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsViewActions.ts @@ -7,7 +7,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { CommentsViewFilterFocusContextKey, ICommentsView } from 'vs/workbench/contrib/comments/browser/comments'; import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -74,9 +74,9 @@ export class CommentsFilters extends Disposable { } } - private _sortBy = CONTEXT_KEY_SORT_BY.bindTo(this.contextKeyService); + private _sortBy: IContextKey = CONTEXT_KEY_SORT_BY.bindTo(this.contextKeyService); get sortBy(): CommentsSortOrder { - return this._sortBy.get()!; + return this._sortBy.get() ?? CommentsSortOrder.ResourceAscending; } set sortBy(sortBy: CommentsSortOrder) { if (this._sortBy.get() !== sortBy) { @@ -208,7 +208,7 @@ registerAction2(class extends ViewAction { icon: Codicon.history, viewId: COMMENTS_VIEW_ID, toggled: { - condition: ContextKeyExpr.equals('commentsView.sortBy', CommentsSortOrder.UpdatedAtDescending), + condition: ContextKeyExpr.equals(CONTEXT_KEY_SORT_BY.key, CommentsSortOrder.UpdatedAtDescending), title: localize('sorting by updated at', "Updated Time"), }, menu: { @@ -229,13 +229,13 @@ registerAction2(class extends ViewAction { constructor() { super({ id: `workbench.actions.${COMMENTS_VIEW_ID}.toggleSortByResource`, - title: localize('toggle sorting by resource', "File"), + title: localize('toggle sorting by resource', "Position in File"), category: localize('comments', "Comments"), icon: Codicon.history, viewId: COMMENTS_VIEW_ID, toggled: { - condition: ContextKeyExpr.equals('commentsView.sortBy', CommentsSortOrder.ResourceAscending), - title: localize('sorting by file', "File"), + condition: ContextKeyExpr.equals(CONTEXT_KEY_SORT_BY.key, CommentsSortOrder.ResourceAscending), + title: localize('sorting by position in file', "Position in File"), }, menu: { id: commentSortSubmenu, From 0e542f326f2985e1ae4dfaeab6be9bd267fb861f Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 28 Aug 2024 13:42:40 -0700 Subject: [PATCH 28/47] fix: populate chat command disambiguation (#227008) --- src/vs/workbench/contrib/chat/common/chatAgents.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 5cc12623309..3ff7e3311bb 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -445,7 +445,7 @@ export class ChatAgentService implements IChatAgentService { const participants = this.getAgents().reduce((acc, a) => { acc.push({ participant: a.id, disambiguation: a.disambiguation ?? [] }); for (const command of a.slashCommands) { - acc.push({ participant: a.id, command: command.name, disambiguation: [] }); + acc.push({ participant: a.id, command: command.name, disambiguation: command.disambiguation ?? [] }); } return acc; }, []); From 913b9b9a2c16f3b8810a32e7cccc534250670c96 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 28 Aug 2024 13:48:57 -0700 Subject: [PATCH 29/47] Tweak organize imports setting descriptions For #225814 - Removes link to presets which haven't yet been finalized - Adds periods at end of descriptions --- .../package.nls.json | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 6b1946b98ad..d2a0ca892fa 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -188,21 +188,21 @@ "typescript.preferences.renameShorthandProperties.deprecationMessage": "The setting 'typescript.preferences.renameShorthandProperties' has been deprecated in favor of 'typescript.preferences.useAliasesForRenames'", "typescript.preferences.useAliasesForRenames": "Enable/disable introducing aliases for object shorthand properties during renames.", "typescript.preferences.renameMatchingJsxTags": "When on a JSX tag, try to rename the matching tag instead of renaming the symbol. Requires using TypeScript 5.1+ in the workspace.", - "typescript.preferences.organizeImports": "Advanced preferences that control how imports are ordered. Presets are available in `#typescript.preferences.organizeImports.presets#`", - "javascript.preferences.organizeImports": "Advanced preferences that control how imports are ordered. Presets are available in `#javascript.preferences.organizeImports.presets#`", - "typescript.preferences.organizeImports.caseSensitivity.auto": "Detect case-sensitivity for import sorting", - "typescript.preferences.organizeImports.caseSensitivity.insensitive": "Sort imports case-insensitively", - "typescript.preferences.organizeImports.caseSensitivity.sensitive": "Sort imports case-sensitively", - "typescript.preferences.organizeImports.typeOrder.auto": "Detect where type-only named imports should be sorted", - "typescript.preferences.organizeImports.typeOrder.last": "Type only named imports are sorted to the end of the import list", - "typescript.preferences.organizeImports.typeOrder.inline": "Named imports are sorted by name only", - "typescript.preferences.organizeImports.typeOrder.first": "Type only named imports are sorted to the end of the import list", - "typescript.preferences.organizeImports.unicodeCollation.ordinal": "Sort imports using the numeric value of each code point", - "typescript.preferences.organizeImports.unicodeCollation.unicode": "Sort imports using the Unicode code collation", - "typescript.preferences.organizeImports.locale": "Overrides the locale used for collation. Specify `auto` to use the UI locale. Only applies to `organizeImportsCollation: 'unicode'`", - "typescript.preferences.organizeImports.caseFirst": "Indicates whether upper-case comes before lower-case. Only applies to `organizeImportsCollation: 'unicode'`", - "typescript.preferences.organizeImports.numericCollation": "Sort numeric strings by integer value", - "typescript.preferences.organizeImports.accentCollation": "Compare characters with diacritical marks as unequal to base character", + "typescript.preferences.organizeImports": "Advanced preferences that control how imports are ordered.", + "javascript.preferences.organizeImports": "Advanced preferences that control how imports are ordered.", + "typescript.preferences.organizeImports.caseSensitivity.auto": "Detect case-sensitivity for import sorting.", + "typescript.preferences.organizeImports.caseSensitivity.insensitive": "Sort imports case-insensitively.", + "typescript.preferences.organizeImports.caseSensitivity.sensitive": "Sort imports case-sensitively.", + "typescript.preferences.organizeImports.typeOrder.auto": "Detect where type-only named imports should be sorted.", + "typescript.preferences.organizeImports.typeOrder.last": "Type only named imports are sorted to the end of the import list.", + "typescript.preferences.organizeImports.typeOrder.inline": "Named imports are sorted by name only.", + "typescript.preferences.organizeImports.typeOrder.first": "Type only named imports are sorted to the end of the import list.", + "typescript.preferences.organizeImports.unicodeCollation.ordinal": "Sort imports using the numeric value of each code point.", + "typescript.preferences.organizeImports.unicodeCollation.unicode": "Sort imports using the Unicode code collation.", + "typescript.preferences.organizeImports.locale": "Overrides the locale used for collation. Specify `auto` to use the UI locale. Only applies to `organizeImportsCollation: 'unicode'`.", + "typescript.preferences.organizeImports.caseFirst": "Indicates whether upper-case comes before lower-case. Only applies to `organizeImportsCollation: 'unicode'`.", + "typescript.preferences.organizeImports.numericCollation": "Sort numeric strings by integer value.", + "typescript.preferences.organizeImports.accentCollation": "Compare characters with diacritical marks as unequal to base character.", "typescript.workspaceSymbols.scope": "Controls which files are searched by [Go to Symbol in Workspace](https://code.visualstudio.com/docs/editor/editingevolved#_open-symbol-by-name).", "typescript.workspaceSymbols.scope.allOpenProjects": "Search all open JavaScript or TypeScript projects for symbols.", "typescript.workspaceSymbols.scope.currentProject": "Only search for symbols in the current JavaScript or TypeScript project.", From 3e0761cc9fe7bb9a70f4284da5ec6f5c46969513 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 28 Aug 2024 14:11:30 -0700 Subject: [PATCH 30/47] Fix setting --- .../typescript-language-features/package.json | 16 ++++++++++++++-- .../languageFeatures/fileConfigurationManager.ts | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index f234a72645a..e93299d8169 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -1116,7 +1116,13 @@ "inline", "first" ], - "default": "auto" + "default": "auto", + "markdownEnumDescriptions": [ + "%typescript.preferences.organizeImports.typeOrder.auto%", + "%typescript.preferences.organizeImports.typeOrder.last%", + "%typescript.preferences.organizeImports.typeOrder.inline%", + "%typescript.preferences.organizeImports.typeOrder.first%" + ] }, "unicodeCollation": { "type": "string", @@ -1180,7 +1186,13 @@ "inline", "first" ], - "default": "auto" + "default": "auto", + "markdownEnumDescriptions": [ + "%typescript.preferences.organizeImports.typeOrder.auto%", + "%typescript.preferences.organizeImports.typeOrder.last%", + "%typescript.preferences.organizeImports.typeOrder.inline%", + "%typescript.preferences.organizeImports.typeOrder.first%" + ] }, "unicodeCollation": { "type": "string", diff --git a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts index 484ea640295..6e1ae9051b1 100644 --- a/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts +++ b/extensions/typescript-language-features/src/languageFeatures/fileConfigurationManager.ts @@ -201,7 +201,7 @@ export default class FileConfigurationManager extends Disposable { interactiveInlayHints: true, includeCompletionsForModuleExports: config.get('suggest.autoImports'), ...getInlayHintsPreferences(config), - ...this.getOrganizeImportsPreferences(config), + ...this.getOrganizeImportsPreferences(preferencesConfig), }; return preferences; From 3e1258a95776874897c7fa1845bd1f29a31296d2 Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 28 Aug 2024 14:17:07 -0700 Subject: [PATCH 31/47] Using cancellation with findTextInFilesNew Fixes #226916 --- src/vs/workbench/api/common/extHostWorkspace.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 33e3bafe5e7..132dbdf61c8 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -664,7 +664,10 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac async findTextInFilesBase(query: vscode.TextSearchQuery, queryOptions: QueryOptions[] | undefined, callback: (result: ITextSearchResult, uri: URI) => void, token: vscode.CancellationToken = CancellationToken.None): Promise { const requestId = this._requestIdProvider.getNext(); - const isCanceled = false; + let isCanceled = false; + token.onCancellationRequested(_ => { + isCanceled = true; + }); this._activeSearchCallbacks[requestId] = p => { if (isCanceled) { From 45b0afbff9d7ed716dde001773d06cf14c49fac0 Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 28 Aug 2024 14:30:53 -0700 Subject: [PATCH 32/47] Exclude not applied Fixes #226714 --- src/vs/workbench/api/common/extHostWorkspace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index 33e3bafe5e7..de86d1e7eea 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -584,7 +584,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac } const parsedInclude = include ? parseSearchExcludeInclude(GlobPattern.from(include)) : undefined; - const excludePatterns = include ? globsToISearchPatternBuilder(options.exclude) : undefined; + const excludePatterns = globsToISearchPatternBuilder(options.exclude); return { options: { From 015878c11ccf76ad1cdfc00d619cbc41d2aad725 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 29 Aug 2024 00:29:36 +0200 Subject: [PATCH 33/47] fix #226725 (#227019) --- .../browser/userDataProfilesEditor.ts | 28 ++++++++++--------- .../browser/userDataProfilesEditorModel.ts | 11 ++++---- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts index 7fa4518bdc5..17ddb9cb36d 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditor.ts @@ -92,6 +92,7 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi private profileWidget: ProfileWidget | undefined; private model: UserDataProfilesEditorModel | undefined; + private templates: readonly IProfileTemplateInfo[] = []; constructor( group: IEditorGroup, @@ -207,7 +208,7 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi actions: { getActions: () => { const actions: IAction[] = []; - if (this.model?.templates.length) { + if (this.templates.length) { actions.push(new SubmenuAction('from.template', localize('from template', "From Template"), this.getCreateFromTemplateActions())); actions.push(new Separator()); } @@ -225,15 +226,13 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi } private getCreateFromTemplateActions(): IAction[] { - return this.model - ? this.model.templates.map(template => - new Action( - `template:${template.url}`, - template.name, - undefined, - true, - () => this.createNewProfile(URI.parse(template.url)))) - : []; + return this.templates.map(template => + new Action( + `template:${template.url}`, + template.name, + undefined, + true, + () => this.createNewProfile(URI.parse(template.url)))); } private registerListeners(): void { @@ -343,9 +342,12 @@ export class UserDataProfilesEditor extends EditorPane implements IUserDataProfi override async setInput(input: UserDataProfilesEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { await super.setInput(input, options, context, token); this.model = await input.resolve(); - if (this.profileWidget) { - this.profileWidget.templates = this.model.templates; - } + this.model.getTemplates().then(templates => { + this.templates = templates; + if (this.profileWidget) { + this.profileWidget.templates = templates; + } + }); this.updateProfilesList(); this._register(this.model.onDidChange(element => this.updateProfilesList(element))); diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts index 42afc9e189d..87827dcfaa0 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilesEditorModel.ts @@ -713,8 +713,7 @@ export class UserDataProfilesEditorModel extends EditorModel { private _onDidChange = this._register(new Emitter()); readonly onDidChange = this._onDidChange.event; - private _templates: IProfileTemplateInfo[] | undefined; - get templates(): readonly IProfileTemplateInfo[] { return this._templates ?? []; } + private templates: Promise | undefined; constructor( @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @@ -761,9 +760,11 @@ export class UserDataProfilesEditorModel extends EditorModel { } } - override async resolve(): Promise { - await super.resolve(); - this._templates = await this.userDataProfileManagementService.getBuiltinProfileTemplates(); + getTemplates(): Promise { + if (!this.templates) { + this.templates = this.userDataProfileManagementService.getBuiltinProfileTemplates(); + } + return this.templates; } private createProfileElement(profile: IUserDataProfile): [UserDataProfileElement, DisposableStore] { From 59b27091944fd8dc39df4ae3ebeaec78f3ae9c65 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Wed, 28 Aug 2024 15:40:21 -0700 Subject: [PATCH 34/47] adjustments to code action styling (#226999) * adjustments to code action styling * add margin top --- src/vs/platform/actionWidget/browser/actionList.ts | 4 ++-- src/vs/platform/actionWidget/browser/actionWidget.css | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/actionWidget/browser/actionList.ts b/src/vs/platform/actionWidget/browser/actionList.ts index 98403a2a4c2..8eeeee2df29 100644 --- a/src/vs/platform/actionWidget/browser/actionList.ts +++ b/src/vs/platform/actionWidget/browser/actionList.ts @@ -166,8 +166,8 @@ export class ActionList extends Disposable { private readonly _list: List>; - private readonly _actionLineHeight = 28; - private readonly _headerLineHeight = 28; + private readonly _actionLineHeight = 24; + private readonly _headerLineHeight = 26; private readonly _allMenuItems: readonly IActionListItem[]; diff --git a/src/vs/platform/actionWidget/browser/actionWidget.css b/src/vs/platform/actionWidget/browser/actionWidget.css index b9ebc031df3..d205c7ab791 100644 --- a/src/vs/platform/actionWidget/browser/actionWidget.css +++ b/src/vs/platform/actionWidget/browser/actionWidget.css @@ -132,8 +132,9 @@ /* Action bar */ .action-widget .action-widget-action-bar { - background-color: var(--vscode-editorHoverWidget-statusBarBackground); + background-color: var(--vscode-editorActionList-background); border-top: 1px solid var(--vscode-editorHoverWidget-border); + margin-top: 2px; } .action-widget .action-widget-action-bar::before { @@ -143,7 +144,7 @@ } .action-widget .action-widget-action-bar .actions-container { - padding: 0 8px; + padding: 3px 8px 0; } .action-widget-action-bar .action-label { From c293d5af4283920efaadcfdec31ac42f421738ef Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Wed, 28 Aug 2024 16:15:56 -0700 Subject: [PATCH 35/47] fix lightbulb jumping in the gutter (but like not the trash) (#227022) * fix for flickering lightbulb * second fix * remove console log --- .../contrib/codeAction/browser/codeActionController.ts | 6 ++++++ src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts index dbbe9a912fd..862459df98c 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionController.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionController.ts @@ -180,6 +180,12 @@ export class CodeActionController extends Disposable implements IEditorContribut return; } + + const selection = this._editor.getSelection(); + if (selection?.startLineNumber !== newState.position.lineNumber) { + return; + } + this._lightBulbWidget.value?.update(actions, newState.trigger, newState.position); if (newState.trigger.type === CodeActionTriggerType.Invoke) { diff --git a/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts index 0395f421600..8f0e98d5bf3 100644 --- a/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/browser/lightBulbWidget.ts @@ -271,7 +271,6 @@ export class LightBulbWidget extends Disposable implements IContentWidget { const currLineEmptyOrIndented = isLineEmptyOrIndented(lineNumber); const notEmpty = !nextLineEmptyOrIndented && !prevLineEmptyOrIndented; - // check above and below. if both are blocked, display lightbulb in the gutter. if (!nextLineEmptyOrIndented && !prevLineEmptyOrIndented && !hasDecoration) { this.gutterState = new LightBulbState.Showing(actions, trigger, atPosition, { @@ -280,7 +279,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { }); this.renderGutterLightbub(); return this.hide(); - } else if (prevLineEmptyOrIndented || endLine || (notEmpty && !currLineEmptyOrIndented)) { + } else if (prevLineEmptyOrIndented || endLine || (prevLineEmptyOrIndented && !currLineEmptyOrIndented)) { effectiveLineNumber -= 1; } else if (nextLineEmptyOrIndented || (notEmpty && currLineEmptyOrIndented)) { effectiveLineNumber += 1; From 0e62f015f7b1a2bc9f8b953c47400b6329ae3c99 Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 28 Aug 2024 17:26:57 -0700 Subject: [PATCH 36/47] Old FileSearchProvider session not properly cancelled and should be object type --- .../search/common/fileSearchManager.ts | 34 ++++++++++++++++--- ...vscode.proposed.fileSearchProviderNew.d.ts | 2 +- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/services/search/common/fileSearchManager.ts b/src/vs/workbench/services/search/common/fileSearchManager.ts index 659217efd5e..73505b7a1fb 100644 --- a/src/vs/workbench/services/search/common/fileSearchManager.ts +++ b/src/vs/workbench/services/search/common/fileSearchManager.ts @@ -13,6 +13,8 @@ import { URI } from 'vs/base/common/uri'; import { IFileMatch, IFileSearchProviderStats, IFolderQuery, ISearchCompleteStats, IFileQuery, QueryGlobTester, resolvePatternsForProvider, hasSiblingFn, excludeToGlobPattern, DEFAULT_MAX_SEARCH_RESULTS } from 'vs/workbench/services/search/common/search'; import { FileSearchProviderFolderOptions, FileSearchProviderNew, FileSearchProviderOptions } from 'vs/workbench/services/search/common/searchExtTypes'; import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { OldFileSearchProviderConverter } from 'vs/workbench/services/search/common/searchExtConversionTypes'; interface IInternalFileMatch { base: URI; @@ -53,7 +55,7 @@ class FileSearchEngine { private globalExcludePattern?: glob.ParsedExpression; - constructor(private config: IFileQuery, private provider: FileSearchProviderNew, private sessionToken?: unknown) { + constructor(private config: IFileQuery, private provider: FileSearchProviderNew, private sessionLifecycle?: SessionLifecycle) { this.filePattern = config.filePattern; this.includePattern = config.includePattern && glob.parse(config.includePattern); this.maxResults = config.maxResults || undefined; @@ -116,10 +118,11 @@ class FileSearchEngine { private async doSearch(fqs: IFolderQuery[], onResult: (match: IInternalFileMatch) => void): Promise { const cancellation = new CancellationTokenSource(); const folderOptions = fqs.map(fq => this.getSearchOptionsForFolder(fq)); + const session = this.provider instanceof OldFileSearchProviderConverter ? this.sessionLifecycle?.tokenSource.token : this.sessionLifecycle?.obj; const options: FileSearchProviderOptions = { folderOptions, maxResults: this.config.maxResults ?? DEFAULT_MAX_SEARCH_RESULTS, - session: this.sessionToken + session }; @@ -301,11 +304,30 @@ interface IInternalSearchComplete { stats?: IFileSearchProviderStats; } +/** + * For backwards compatibility, store both a cancellation token and a session object. The session object is the new implementation, where + */ +class SessionLifecycle extends Disposable { + public readonly obj: object; + public readonly tokenSource: CancellationTokenSource; + + constructor() { + super(); + this.obj = new Object(); + this.tokenSource = new CancellationTokenSource(); + } + + public override dispose(): void { + this.tokenSource.cancel(); + super.dispose(); + } +} + export class FileSearchManager { private static readonly BATCH_SIZE = 512; - private readonly sessions = new Map(); + private readonly sessions = new Map(); fileSearch(config: IFileQuery, provider: FileSearchProviderNew, onBatch: (matches: IFileMatch[]) => void, token: CancellationToken): Promise { const sessionTokenSource = this.getSessionTokenSource(config.cacheKey); @@ -333,17 +355,19 @@ export class FileSearchManager { } clearCache(cacheKey: string): void { + // cancel the token + this.sessions.get(cacheKey)?.dispose(); // with no reference to this, it will be removed from WeakMaps this.sessions.delete(cacheKey); } - private getSessionTokenSource(cacheKey: string | undefined): unknown { + private getSessionTokenSource(cacheKey: string | undefined): SessionLifecycle | undefined { if (!cacheKey) { return undefined; } if (!this.sessions.has(cacheKey)) { - this.sessions.set(cacheKey, new Object()); + this.sessions.set(cacheKey, new SessionLifecycle()); } return this.sessions.get(cacheKey); diff --git a/src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts b/src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts index efc6d52dca5..36b0b482313 100644 --- a/src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts +++ b/src/vscode-dts/vscode.proposed.fileSearchProviderNew.d.ts @@ -57,7 +57,7 @@ declare module 'vscode' { * and searches with the same session object can search the same cache. When the object is garbage-collected, the session is complete and the cache can be cleared. * Please do not store any references to the session object, except via a weak reference (e.g. `WeakRef` or `WeakMap`). */ - session: unknown; + session: object; /** * The maximum number of results to be returned. From 8cd1d86cb7f4cc0291e8b7ccf21cb68d961168c5 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 28 Aug 2024 18:51:38 -0700 Subject: [PATCH 37/47] Fix slash comands in /help that don't have sample queries (#227027) Fix microsoft/vscode-copilot#7742 --- src/vs/workbench/contrib/chat/common/chatServiceImpl.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 3ca4524e80e..7082bc267cd 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -481,9 +481,8 @@ export class ChatService extends Disposable implements IChatService { } async sendRequest(sessionId: string, request: string, options?: IChatSendRequestOptions): Promise { - this.trace('sendRequest', `sessionId: ${sessionId}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`); - if (!request.trim()) { + if (!request.trim() && !options?.slashCommand && !options?.agentId) { this.trace('sendRequest', 'Rejected empty message'); return; } From 4ab6e5d7c34997512793dd58ed8516ce1d72e5a4 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 28 Aug 2024 19:15:04 -0700 Subject: [PATCH 38/47] Properly normalize very old chat session data (#227030) Fix microsoft/vscode-copilot#7729 --- .../contrib/chat/common/chatModel.ts | 26 ++++++++++ .../chat/test/common/chatModel.test.ts | 51 ++++++++++++++++++- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 14a94c046d7..b9578baed08 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -616,6 +616,8 @@ export type ISerializableChatDataIn = ISerializableChatData1 | ISerializableChat * TODO- ChatModel#_deserialize and reviveSerializedAgent also still do some normalization and maybe that should be done in here too. */ export function normalizeSerializableChatData(raw: ISerializableChatDataIn): ISerializableChatData { + normalizeOldFields(raw); + if (!('version' in raw)) { return { version: 3, @@ -636,6 +638,30 @@ export function normalizeSerializableChatData(raw: ISerializableChatDataIn): ISe return raw; } +function normalizeOldFields(raw: ISerializableChatDataIn): void { + // Fill in fields that very old chat data may be missing + if (!raw.sessionId) { + raw.sessionId = generateUuid(); + } + + if (!raw.creationDate) { + raw.creationDate = getLastYearDate(); + } + + if ('version' in raw && (raw.version === 2 || raw.version === 3)) { + if (!raw.lastMessageDate) { + // A bug led to not porting creationDate properly, and that was copied to lastMessageDate, so fix that up if missing. + raw.lastMessageDate = getLastYearDate(); + } + } +} + +function getLastYearDate(): number { + const lastYearDate = new Date(); + lastYearDate.setFullYear(lastYearDate.getFullYear() - 1); + return lastYearDate.getTime(); +} + export function isExportableSessionData(obj: unknown): obj is IExportableChatData { const data = obj as IExportableChatData; return typeof data === 'object' && diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index 4be9380fe47..c37b05e8a89 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -17,7 +17,7 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatAgentLocation, ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { ChatModel, ISerializableChatData1, ISerializableChatData2, normalizeSerializableChatData, Response } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatModel, ISerializableChatData1, ISerializableChatData2, ISerializableChatData3, normalizeSerializableChatData, Response } from 'vs/workbench/contrib/chat/common/chatModel'; import { ChatRequestTextPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -230,4 +230,53 @@ suite('normalizeSerializableChatData', () => { assert.strictEqual(newData.lastMessageDate, v2Data.lastMessageDate); assert.strictEqual(newData.customTitle, v2Data.computedTitle); }); + + test('old bad data', () => { + const v1Data: ISerializableChatData1 = { + // Testing the scenario where these are missing + sessionId: undefined!, + creationDate: undefined!, + + initialLocation: undefined, + isImported: false, + requesterAvatarIconUri: undefined, + requesterUsername: 'me', + requests: [], + responderAvatarIconUri: undefined, + responderUsername: 'bot', + welcomeMessage: [] + }; + + const newData = normalizeSerializableChatData(v1Data); + assert.strictEqual(newData.version, 3); + assert.ok(newData.creationDate > 0); + assert.ok(newData.lastMessageDate > 0); + assert.ok(newData.sessionId); + }); + + test('v3 with bug', () => { + const v3Data: ISerializableChatData3 = { + // Test case where old data was wrongly normalized and these fields were missing + creationDate: undefined!, + lastMessageDate: undefined!, + + version: 3, + initialLocation: undefined, + isImported: false, + requesterAvatarIconUri: undefined, + requesterUsername: 'me', + requests: [], + responderAvatarIconUri: undefined, + responderUsername: 'bot', + sessionId: 'session1', + welcomeMessage: [], + customTitle: 'computed title' + }; + + const newData = normalizeSerializableChatData(v3Data); + assert.strictEqual(newData.version, 3); + assert.ok(newData.creationDate > 0); + assert.ok(newData.lastMessageDate > 0); + assert.ok(newData.sessionId); + }); }); From 80ef8fe939714ecc5b8fd3623cb3d23375a11035 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 28 Aug 2024 19:24:31 -0700 Subject: [PATCH 39/47] fix: freeze chat history before doing intent detection (#227028) * fix: freeze chat history before doing intent detection * fix: restore title provider dependence on chat request already being added --- .../contrib/chat/common/chatServiceImpl.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 7082bc267cd..2b6cfac741c 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -545,6 +545,7 @@ export class ChatService extends Disposable implements IChatService { const agentPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart); const agentSlashCommandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart); const commandPart = 'kind' in parsedRequest ? undefined : parsedRequest.parts.find((r): r is ChatRequestSlashCommandPart => r instanceof ChatRequestSlashCommandPart); + const requests = [...model.getRequests()]; let gotProgress = false; const requestType = commandPart ? 'slashCommand' : 'string'; @@ -638,7 +639,7 @@ export class ChatService extends Disposable implements IChatService { if (this.configurationService.getValue('chat.experimental.detectParticipant.enabled') !== false && this.chatAgentService.hasChatParticipantDetectionProviders() && !agentPart && !commandPart && enableCommandDetection) { // We have no agent or command to scope history with, pass the full history to the participant detection provider - const defaultAgentHistory = this.getHistoryEntriesFromModel(model, location, defaultAgent.id); + const defaultAgentHistory = this.getHistoryEntriesFromModel(requests, model.sessionId, location, defaultAgent.id); // Prepare the request object that we will send to the participant detection provider const chatAgentRequest = await prepareChatAgentRequest(defaultAgent, agentSlashCommandPart?.command, enableCommandDetection, undefined, false); @@ -657,7 +658,7 @@ export class ChatService extends Disposable implements IChatService { await this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`); // Recompute history in case the agent or command changed - const history = this.getHistoryEntriesFromModel(model, location, agent.id); + const history = this.getHistoryEntriesFromModel(requests, model.sessionId, location, agent.id); const requestProps = await prepareChatAgentRequest(agent, command, enableCommandDetection, request /* Reuse the request object if we already created it for participant detection */, !!detectedAgent); const pendingRequest = this._pendingRequests.get(sessionId); if (pendingRequest && !pendingRequest.requestId) { @@ -667,7 +668,7 @@ export class ChatService extends Disposable implements IChatService { const agentResult = await this.chatAgentService.invokeAgent(agent.id, requestProps, progressCallback, history, token); rawResult = agentResult; agentOrCommandFollowups = this.chatAgentService.getFollowups(agent.id, requestProps, agentResult, history, followupsCancelToken); - chatTitlePromise = model.getRequests().length === 1 && !model.customTitle ? this.chatAgentService.getChatTitle(defaultAgent.id, this.getHistoryEntriesFromModel(model, location, agent.id), CancellationToken.None) : undefined; + chatTitlePromise = model.getRequests().length === 1 && !model.customTitle ? this.chatAgentService.getChatTitle(defaultAgent.id, this.getHistoryEntriesFromModel(model.getRequests(), model.sessionId, location, agent.id), CancellationToken.None) : undefined; } else if (commandPart && this.chatSlashCommandService.hasCommand(commandPart.slashCommand.command)) { request = model.addRequest(parsedRequest, { variables: [] }, attempt); completeResponseCreated(); @@ -774,9 +775,9 @@ export class ChatService extends Disposable implements IChatService { }; } - private getHistoryEntriesFromModel(model: IChatModel, location: ChatAgentLocation, forAgentId: string): IChatAgentHistoryEntry[] { + private getHistoryEntriesFromModel(requests: IChatRequestModel[], sessionId: string, location: ChatAgentLocation, forAgentId: string): IChatAgentHistoryEntry[] { const history: IChatAgentHistoryEntry[] = []; - for (const request of model.getRequests()) { + for (const request of requests) { if (!request.response) { continue; } @@ -790,7 +791,7 @@ export class ChatService extends Disposable implements IChatService { const promptTextResult = getPromptText(request.message); const historyRequest: IChatAgentRequest = { - sessionId: model.sessionId, + sessionId: sessionId, requestId: request.id, agentId: request.response.agent?.id ?? '', message: promptTextResult.message, From ae45c9d4b0f71d53151edc6d18be09107903c229 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 28 Aug 2024 21:21:48 -0700 Subject: [PATCH 40/47] Add welcome view with warning for chat extension with invalid API version (#227020) * Add welcome view with warning for chat extension with invalid API version Fix #218646 * undo * Remove unused interface --- .../contrib/chat/browser/chat.contribution.ts | 6 +- .../browser/chatParticipantContributions.ts | 98 +++++++++---------- .../contrib/chat/browser/chatViewPane.ts | 7 +- .../contrib/chat/common/chatAgents.ts | 12 ++- .../contrib/chat/common/chatContextKeys.ts | 4 +- 5 files changed, 70 insertions(+), 57 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index d5cf3c355e0..be6c4955294 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -36,7 +36,7 @@ import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat import { ChatEditor, IChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatEditor'; import { ChatEditorInput, ChatEditorInputSerializer } from 'vs/workbench/contrib/chat/browser/chatEditorInput'; import { agentSlashCommandToMarkdown, agentToMarkdown } from 'vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer'; -import { ChatExtensionPointHandler } from 'vs/workbench/contrib/chat/browser/chatParticipantContributions'; +import { ChatCompatibilityNotifier, ChatExtensionPointHandler } from 'vs/workbench/contrib/chat/browser/chatParticipantContributions'; import { QuickChatService } from 'vs/workbench/contrib/chat/browser/chatQuick'; import { ChatResponseAccessibleView } from 'vs/workbench/contrib/chat/browser/chatResponseAccessibleView'; import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; @@ -261,9 +261,7 @@ workbenchContributionsRegistry.registerWorkbenchContribution(ChatSlashStaticSlas Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer(ChatEditorInput.TypeID, ChatEditorInputSerializer); registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup); registerWorkbenchContribution2(LanguageModelToolsExtensionPointHandler.ID, LanguageModelToolsExtensionPointHandler, WorkbenchPhase.BlockRestore); - -// Disabled until https://github.com/microsoft/vscode/issues/218646 is fixed -// registerWorkbenchContribution2(ChatCompatibilityNotifier.ID, ChatCompatibilityNotifier, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(ChatCompatibilityNotifier.ID, ChatCompatibilityNotifier, WorkbenchPhase.Eventually); registerChatActions(); registerChatCopyActions(); diff --git a/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts b/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts index 09a83daf318..2bf50f4a8cf 100644 --- a/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatParticipantContributions.ts @@ -3,17 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Action } from 'vs/base/common/actions'; import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; -import { Disposable, DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableMap, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { localize, localize2 } from 'vs/nls'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ILogService } from 'vs/platform/log/common/log'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { Severity } from 'vs/platform/notification/common/notification'; import { Registry } from 'vs/platform/registry/common/platform'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -21,7 +20,9 @@ import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer import { CHAT_VIEW_ID } from 'vs/workbench/contrib/chat/browser/chat'; import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { ChatAgentLocation, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { CONTEXT_CHAT_EXTENSION_INVALID, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IRawChatParticipantContribution } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; +import { showExtensionsWithIdsCommandId } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry'; @@ -160,35 +161,6 @@ const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.regi }, }); -export class ChatCompatibilityNotifier implements IWorkbenchContribution { - static readonly ID = 'workbench.contrib.chatCompatNotifier'; - - constructor( - @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, - @INotificationService notificationService: INotificationService, - @ICommandService commandService: ICommandService - ) { - // It may be better to have some generic UI for this, for any extension that is incompatible, - // but this is only enabled for Copilot Chat now and it needs to be obvious. - extensionsWorkbenchService.queryLocal().then(exts => { - const chat = exts.find(ext => ext.identifier.id === 'github.copilot-chat'); - if (chat?.local?.validations.some(v => v[0] === Severity.Error)) { - notificationService.notify({ - severity: Severity.Error, - message: localize('chatFailErrorMessage', "Chat failed to load. Please ensure that the GitHub Copilot Chat extension is up to date."), - actions: { - primary: [ - new Action('showExtension', localize('action.showExtension', "Show Extension"), undefined, true, () => { - return commandService.executeCommand('workbench.extensions.action.showExtensionsWithIds', ['GitHub.copilot-chat']); - }) - ] - } - }); - } - }); - } -} - export class ChatExtensionPointHandler implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.chatExtensionPointHandler'; @@ -198,9 +170,10 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { constructor( @IChatAgentService private readonly _chatAgentService: IChatAgentService, - @ILogService private readonly logService: ILogService, + @ILogService private readonly logService: ILogService ) { this._viewContainer = this.registerViewContainer(); + this.registerDefaultParticipantView(); this.handleAndRegisterChatExtensions(); } @@ -239,11 +212,6 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { continue; } - const store = new DisposableStore(); - if (providerDescriptor.isDefault && (!providerDescriptor.locations || providerDescriptor.locations?.includes(ChatAgentLocation.Panel))) { - store.add(this.registerDefaultParticipantView(providerDescriptor)); - } - const participantsAndCommandsDisambiguation: { categoryName: string; description: string; @@ -260,6 +228,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { } } + const store = new DisposableStore(); store.add(this._chatAgentService.registerAgent( providerDescriptor.id, { @@ -318,15 +287,9 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { return viewContainer; } - private hasRegisteredDefaultParticipantView = false; - private registerDefaultParticipantView(defaultParticipantDescriptor: IRawChatParticipantContribution): IDisposable { - if (this.hasRegisteredDefaultParticipantView) { - this.logService.warn(`Tried to register a second default chat participant view for "${defaultParticipantDescriptor.id}"`); - return Disposable.None; - } - - // Register View - const name = defaultParticipantDescriptor.fullName ?? defaultParticipantDescriptor.name; + private registerDefaultParticipantView(): IDisposable { + // Register View. Name must be hardcoded because we want to show it even when the extension fails to load due to an API version incompatibility. + const name = 'GitHub Copilot'; const viewDescriptor: IViewDescriptor[] = [{ id: CHAT_VIEW_ID, containerIcon: this._viewContainer.icon, @@ -336,12 +299,11 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { canToggleVisibility: false, canMoveView: true, ctorDescriptor: new SyncDescriptor(ChatViewPane), + when: ContextKeyExpr.or(CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED, CONTEXT_CHAT_EXTENSION_INVALID) }]; - this.hasRegisteredDefaultParticipantView = true; Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, this._viewContainer); return toDisposable(() => { - this.hasRegisteredDefaultParticipantView = false; Registry.as(ViewExtensions.ViewsRegistry).deregisterViews(viewDescriptor, this._viewContainer); }); } @@ -350,3 +312,39 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { function getParticipantKey(extensionId: ExtensionIdentifier, participantName: string): string { return `${extensionId.value}_${participantName}`; } + +export class ChatCompatibilityNotifier implements IWorkbenchContribution { + static readonly ID = 'workbench.contrib.chatCompatNotifier'; + + constructor( + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IContextKeyService contextKeyService: IContextKeyService, + @IChatAgentService chatAgentService: IChatAgentService, + ) { + // It may be better to have some generic UI for this, for any extension that is incompatible, + // but this is only enabled for Copilot Chat now and it needs to be obvious. + + const showExtensionLabel = localize('showExtension', "Show Extension"); + const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); + viewsRegistry.registerViewWelcomeContent(CHAT_VIEW_ID, { + content: localize('chatFailErrorMessage', "Chat failed to load. Please ensure that the GitHub Copilot Chat extension is up to date.") + `\n\n[${showExtensionLabel}](command:${showExtensionsWithIdsCommandId}?${encodeURIComponent(JSON.stringify([['GitHub.copilot-chat']]))})`, + when: CONTEXT_CHAT_EXTENSION_INVALID, + }); + + const isInvalid = CONTEXT_CHAT_EXTENSION_INVALID.bindTo(contextKeyService); + extensionsWorkbenchService.queryLocal().then(exts => { + const chat = exts.find(ext => ext.identifier.id === 'github.copilot-chat'); + if (chat?.local?.validations.some(v => v[0] === Severity.Error)) { + // This catches vscode starting up with the invalid extension, but the extension may still get updated by vscode after this. + isInvalid.set(true); + } + }); + + const listener = chatAgentService.onDidChangeAgents(() => { + if (chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) { + isInvalid.set(false); + listener.dispose(); + } + }); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 0cb92990dff..aa2655cba87 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -88,8 +88,9 @@ export class ChatViewPane extends ViewPane { } else if (this._widget?.viewModel?.initState === ChatModelInitState.Initialized) { // Model is initialized, and the default agent disappeared, so show welcome view this.didUnregisterProvider = true; - this._onDidChangeViewWelcomeState.fire(); } + + this._onDidChangeViewWelcomeState.fire(); })); } @@ -114,6 +115,10 @@ export class ChatViewPane extends ViewPane { } override shouldShowWelcome(): boolean { + if (!this.chatAgentService.getDefaultAgent(ChatAgentLocation.Panel)) { + return true; + } + const noPersistedSessions = !this.chatService.hasSessions(); return this.didUnregisterProvider || !this._widget?.viewModel && (noPersistedSessions || this.didProviderRegistrationFail); } diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 3ff7e3311bb..f565b5b9266 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -24,7 +24,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { asJson, IRequestService } from 'vs/platform/request/common/request'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { CONTEXT_CHAT_ENABLED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_CHAT_ENABLED, CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatProgressResponseContent, IChatRequestVariableData, ISerializableChatAgentData } from 'vs/workbench/contrib/chat/common/chatModel'; import { IRawChatCommandContribution, RawChatParticipantLocation } from 'vs/workbench/contrib/chat/common/chatParticipantContribTypes'; import { IChatFollowup, IChatLocationData, IChatProgress, IChatResponseErrorDetails, IChatTaskDto } from 'vs/workbench/contrib/chat/common/chatService'; @@ -233,11 +233,13 @@ export class ChatAgentService implements IChatAgentService { readonly onDidChangeAgents: Event = this._onDidChangeAgents.event; private readonly _hasDefaultAgent: IContextKey; + private readonly _defaultAgentRegistered: IContextKey; constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { this._hasDefaultAgent = CONTEXT_CHAT_ENABLED.bindTo(this.contextKeyService); + this._defaultAgentRegistered = CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED.bindTo(this.contextKeyService); } registerAgent(id: string, data: IChatAgentData): IDisposable { @@ -246,6 +248,10 @@ export class ChatAgentService implements IChatAgentService { throw new Error(`Agent already registered: ${JSON.stringify(id)}`); } + if (data.isDefault) { + this._defaultAgentRegistered.set(true); + } + const that = this; const commands = data.slashCommands; data = { @@ -258,6 +264,10 @@ export class ChatAgentService implements IChatAgentService { this._agents.set(id, entry); return toDisposable(() => { this._agents.delete(id); + if (data.isDefault) { + this._defaultAgentRegistered.set(false); + } + this._onDidChangeAgents.fire(undefined); }); } diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 5930a97566c..f651a114a44 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -25,7 +25,9 @@ export const CONTEXT_CHAT_INPUT_HAS_FOCUS = new RawContextKey('chatInpu export const CONTEXT_IN_CHAT_INPUT = new RawContextKey('inChatInput', false, { type: 'boolean', description: localize('inInteractiveInput', "True when focus is in the chat input, false otherwise.") }); export const CONTEXT_IN_CHAT_SESSION = new RawContextKey('inChat', false, { type: 'boolean', description: localize('inChat', "True when focus is in the chat widget, false otherwise.") }); -export const CONTEXT_CHAT_ENABLED = new RawContextKey('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is registered.") }); +export const CONTEXT_CHAT_ENABLED = new RawContextKey('chatIsEnabled', false, { type: 'boolean', description: localize('chatIsEnabled', "True when chat is enabled because a default chat participant is activated with an implementation.") }); +export const CONTEXT_CHAT_PANEL_PARTICIPANT_REGISTERED = new RawContextKey('chatPanelParticipantRegistered', false, { type: 'boolean', description: localize('chatParticipantRegistered', "True when a default chat participant is registered for the panel.") }); +export const CONTEXT_CHAT_EXTENSION_INVALID = new RawContextKey('chatExtensionInvalid', false, { type: 'boolean', description: localize('chatExtensionInvalid', "True when the installed chat extension is invalid and needs to be updated.") }); export const CONTEXT_CHAT_INPUT_CURSOR_AT_TOP = new RawContextKey('chatCursorAtTop', false); export const CONTEXT_CHAT_INPUT_HAS_AGENT = new RawContextKey('chatInputHasAgent', false); export const CONTEXT_CHAT_LOCATION = new RawContextKey('chatLocation', undefined); From 22c7f8f3be23b607100f8cc6d42f0e84c5131e12 Mon Sep 17 00:00:00 2001 From: andreamah Date: Wed, 28 Aug 2024 22:17:03 -0700 Subject: [PATCH 41/47] using `RelativePattern` exclude and pattern causes an error Fixes #227035 --- src/vs/workbench/api/browser/mainThreadWorkspace.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadWorkspace.ts b/src/vs/workbench/api/browser/mainThreadWorkspace.ts index 12441286ddc..880a6026dc7 100644 --- a/src/vs/workbench/api/browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/browser/mainThreadWorkspace.ts @@ -28,6 +28,7 @@ import { IEditSessionIdentityService } from 'vs/platform/workspace/common/editSe import { EditorResourceAccessor, SaveReason, SideBySideEditor } from 'vs/workbench/common/editor'; import { coalesce, firstOrDefault } from 'vs/base/common/arrays'; import { ICanonicalUriService } from 'vs/platform/workspace/common/canonicalUri'; +import { revive } from 'vs/base/common/marshalling'; @extHostNamedCustomer(MainContext.MainThreadWorkspace) export class MainThreadWorkspace implements MainThreadWorkspaceShape { @@ -146,7 +147,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { const query = this._queryBuilder.file( includeFolder ? [includeFolder] : workspace.folders, - options + revive(options) ); return this._searchService.fileSearch(query, token).then(result => { @@ -164,7 +165,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { const workspace = this._contextService.getWorkspace(); const folders = folder ? [folder] : workspace.folders.map(folder => folder.uri); - const query = this._queryBuilder.text(pattern, folders, options); + const query = this._queryBuilder.text(pattern, folders, revive(options)); query._reason = 'startTextSearch'; const onProgress = (p: ISearchProgressItem) => { From 1cc150cf084519b0fe8745a0925b8f1db8f28a3f Mon Sep 17 00:00:00 2001 From: Kevin <110841314+stalematker@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:42:24 +0900 Subject: [PATCH 42/47] Fix typo in extensionEnablementService.ts (#224145) Fixing typo manger -> manager Co-authored-by: Sandeep Somavarapu --- .../common/extensionEnablementService.ts | 10 +++++----- .../browser/extensionEnablementService.ts | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts index 61c8816e3aa..7637899aea1 100644 --- a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts @@ -16,15 +16,15 @@ export class GlobalExtensionEnablementService extends Disposable implements IGlo private _onDidChangeEnablement = new Emitter<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }>(); readonly onDidChangeEnablement: Event<{ readonly extensions: IExtensionIdentifier[]; readonly source?: string }> = this._onDidChangeEnablement.event; - private readonly storageManger: StorageManager; + private readonly storageManager: StorageManager; constructor( @IStorageService storageService: IStorageService, @IExtensionManagementService extensionManagementService: IExtensionManagementService, ) { super(); - this.storageManger = this._register(new StorageManager(storageService)); - this._register(this.storageManger.onDidChange(extensions => this._onDidChangeEnablement.fire({ extensions, source: 'storage' }))); + this.storageManager = this._register(new StorageManager(storageService)); + this._register(this.storageManager.onDidChange(extensions => this._onDidChangeEnablement.fire({ extensions, source: 'storage' }))); this._register(extensionManagementService.onDidInstallExtensions(e => e.forEach(({ local, operation }) => { if (local && operation === InstallOperation.Migrate) { this._removeFromDisabledExtensions(local.identifier); /* Reset migrated extensions */ @@ -84,11 +84,11 @@ export class GlobalExtensionEnablementService extends Disposable implements IGlo } private _getExtensions(storageId: string): IExtensionIdentifier[] { - return this.storageManger.get(storageId, StorageScope.PROFILE); + return this.storageManager.get(storageId, StorageScope.PROFILE); } private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void { - this.storageManger.set(storageId, extensions, StorageScope.PROFILE); + this.storageManager.set(storageId, extensions, StorageScope.PROFILE); } } diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts index bbbd1d0b177..ec37cfccaa5 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts @@ -41,7 +41,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench public readonly onEnablementChanged: Event = this._onEnablementChanged.event; protected readonly extensionsManager: ExtensionsManager; - private readonly storageManger: StorageManager; + private readonly storageManager: StorageManager; constructor( @IStorageService storageService: IStorageService, @@ -63,7 +63,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench @IInstantiationService instantiationService: IInstantiationService, ) { super(); - this.storageManger = this._register(new StorageManager(storageService)); + this.storageManager = this._register(new StorageManager(storageService)); const uninstallDisposable = this._register(Event.filter(extensionManagementService.onDidUninstallExtension, e => !e.error)(({ identifier }) => this._reset(identifier))); let isDisposed = false; @@ -610,11 +610,11 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench if (!this.hasWorkspace) { return []; } - return this.storageManger.get(storageId, StorageScope.WORKSPACE); + return this.storageManager.get(storageId, StorageScope.WORKSPACE); } private _setExtensions(storageId: string, extensions: IExtensionIdentifier[]): void { - this.storageManger.set(storageId, extensions, StorageScope.WORKSPACE); + this.storageManager.set(storageId, extensions, StorageScope.WORKSPACE); } private async _onDidChangeGloballyDisabledExtensions(extensionIdentifiers: ReadonlyArray, source?: string): Promise { From c95571667318a8ff30e27e7302f29cc6318a4001 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 09:17:57 +0200 Subject: [PATCH 43/47] Bump micromatch from 4.0.5 to 4.0.8 in /remote (#227016) Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8. - [Release notes](https://github.com/micromatch/micromatch/releases) - [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8) --- updated-dependencies: - dependency-name: micromatch dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- remote/yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/remote/yarn.lock b/remote/yarn.lock index f767466b3d5..74b8c01c013 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -204,7 +204,7 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" -braces@^3.0.2: +braces@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -410,11 +410,11 @@ lru-cache@^6.0.0: yallist "^4.0.0" micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mimic-response@^3.1.0: From 65ce95d65253e850abf33e585bce4f3ea375ae3c Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 29 Aug 2024 10:54:44 +0200 Subject: [PATCH 44/47] fix https://github.com/microsoft/vscode/issues/227045 (#227048) --- .../contrib/inlineChat/browser/inlineChatStrategies.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 28e5b29c7a9..01df414ec2a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -582,9 +582,9 @@ export class LiveStrategy extends EditModeStrategy { data.lensActionsViewZoneIds?.forEach(viewZoneAccessor.removeZone); data.lensActionsViewZoneIds = undefined; - lensActions?.dispose(); }); + lensActions?.dispose(); overlay?.dispose(); }; From cf26b311e6aa1a599d4a60277eb5628bce3028f5 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 29 Aug 2024 12:27:22 +0200 Subject: [PATCH 45/47] stop scrolling/revealing inline chat if "scrolling up" happened (#227055) fixes https://github.com/microsoft/vscode-copilot/issues/7550 --- .../browser/inlineChatZoneWidget.ts | 67 +++++++++++++++++-- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts index 1ce7b490450..42612f493f8 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatZoneWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { addDisposableListener, Dimension } from 'vs/base/browser/dom'; import * as aria from 'vs/base/browser/ui/aria/aria'; -import { toDisposable } from 'vs/base/common/lifecycle'; +import { MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorLayoutInfo, EditorOption } from 'vs/editor/common/config/editorOptions'; @@ -29,6 +29,7 @@ export class InlineChatZoneWidget extends ZoneWidget { readonly widget: EditorBasedInlineChatWidget; + private readonly _scrollUp = this._disposables.add(new ScrollUpState(this.editor)); private readonly _ctxCursorPosition: IContextKey<'above' | 'below' | ''>; private _dimension?: Dimension; @@ -165,6 +166,7 @@ export class InlineChatZoneWidget extends ZoneWidget { this.widget.focus(); revealZone(); + this._scrollUp.enable(); } override updatePositionAndHeight(position: Position): void { @@ -186,14 +188,15 @@ export class InlineChatZoneWidget extends ZoneWidget { return isResponseVM(candidate) && candidate.response.value.length > 0; }); - if (hasResponse && zoneTop < scrollTop) { + if (hasResponse && zoneTop < scrollTop || this._scrollUp.didScrollUp) { // don't reveal the zone if it is already out of view (unless we are still getting ready) - return () => { + // or if an outside scroll-up happened (e.g the user scrolled up to see the new content) + return this._scrollUp.runIgnored(() => { scrollState.restore(this.editor); - }; + }); } - return () => { + return this._scrollUp.runIgnored(() => { scrollState.restore(this.editor); const scrollTop = this.editor.getScrollTop(); @@ -216,7 +219,7 @@ export class InlineChatZoneWidget extends ZoneWidget { this._logService.trace('[IE] REVEAL zone', { zoneTop, lineTop, lineBottom, scrollTop, newScrollTop, forceScrollTop }); this.editor.setScrollTop(newScrollTop, ScrollType.Immediate); } - }; + }); } protected override revealRange(range: Range, isLastLine: boolean): void { @@ -229,6 +232,7 @@ export class InlineChatZoneWidget extends ZoneWidget { override hide(): void { const scrollState = StableEditorBottomScrollState.capture(this.editor); + this._scrollUp.disable(); this._ctxCursorPosition.reset(); this.widget.reset(); this.widget.chatWidget.setVisible(false); @@ -237,3 +241,54 @@ export class InlineChatZoneWidget extends ZoneWidget { scrollState.restore(this.editor); } } + +class ScrollUpState { + + private _lastScrollTop: number = this._editor.getScrollTop(); + private _didScrollUp?: boolean; + private _ignoreEvents = false; + + private readonly _listener = new MutableDisposable(); + + constructor(private readonly _editor: ICodeEditor) { } + + dispose(): void { + this._listener.dispose(); + } + + enable(): void { + this._didScrollUp = undefined; + this._listener.value = this._editor.onDidScrollChange(e => { + if (!e.scrollTopChanged || this._ignoreEvents) { + return; + } + const currentScrollTop = e.scrollTop; + if (currentScrollTop > this._lastScrollTop) { + this._listener.clear(); + this._didScrollUp = true; + } + this._lastScrollTop = currentScrollTop; + }); + } + + disable(): void { + this._listener.clear(); + this._didScrollUp = undefined; + } + + runIgnored(callback: () => void): () => void { + return () => { + this._ignoreEvents = true; + try { + return callback(); + } finally { + this._ignoreEvents = false; + } + }; + } + + get didScrollUp(): boolean | undefined { + return this._didScrollUp; + } + +} From 724ebf4d61ac04276bbb7fb4cb704874040ce1e9 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 29 Aug 2024 12:27:41 +0200 Subject: [PATCH 46/47] Joh/friendly-hedgehog (#227056) * Revert "disable code lense for the output editor (#226971)" This reverts commit dea44122439ea522fa19c30463bca1a5b97fc558. * ignore `output` scheme when logging code lens request --- src/vs/workbench/api/common/extHostLanguageFeatures.ts | 6 +++--- src/vs/workbench/contrib/output/browser/outputView.ts | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 50ab3ea6729..5bb629a6c09 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -2312,15 +2312,15 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF } $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise { - return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined, token); + return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token), undefined, token, resource.scheme === 'output'); } $resolveCodeLens(handle: number, symbol: extHostProtocol.ICodeLensDto, token: CancellationToken): Promise { - return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined, undefined); + return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(symbol, token), undefined, undefined, true); } $releaseCodeLenses(handle: number, cacheId: number): void { - this._withAdapter(handle, CodeLensAdapter, adapter => Promise.resolve(adapter.releaseCodeLenses(cacheId)), undefined, undefined); + this._withAdapter(handle, CodeLensAdapter, adapter => Promise.resolve(adapter.releaseCodeLenses(cacheId)), undefined, undefined, true); } // --- declaration diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts index 50d0ab01a9c..9a23e945d85 100644 --- a/src/vs/workbench/contrib/output/browser/outputView.ts +++ b/src/vs/workbench/contrib/output/browser/outputView.ts @@ -185,7 +185,6 @@ class OutputEditor extends AbstractTextResourceEditor { options.lineDecorationsWidth = 20; options.rulers = []; options.folding = false; - options.codeLens = false; options.scrollBeyondLastLine = false; options.renderLineHighlight = 'none'; options.minimap = { enabled: false }; From abf9cb91c62d026e17db8ebf3d047dfed5b8f70f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 29 Aug 2024 12:31:48 +0200 Subject: [PATCH 47/47] don't move inline chat into target editor if there is already an active session (#227062) fixes https://github.com/microsoft/vscode/issues/227052 --- .../contrib/inlineChat/browser/inlineChatController.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 18e4a817bfa..c6d9ae577fb 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -622,6 +622,7 @@ export class InlineChatController implements IEditorContribution { return; } if (e.kind === 'move') { + assertType(this._session); const log: typeof this._log = (msg: string, ...args: any[]) => this._log('state=_showRequest) moving inline chat', msg, ...args); log('move was requested', e.target, e.range); @@ -636,13 +637,13 @@ export class InlineChatController implements IEditorContribution { } const newEditor = editorPane.getControl(); - if (!newEditor || !isCodeEditor(newEditor) || !newEditor.hasModel()) { + if (!isCodeEditor(newEditor) || !newEditor.hasModel()) { log('new editor is either missing or not a code editor or does not have a model'); return; } - if (!this._session) { - log('controller does not have a session'); + if (this._inlineChatSessionService.getSession(newEditor, e.target)) { + log('new editor ALREADY has a session'); return; }