From 9997c23708a831f1d9ec4ac63468cdb60b8873c1 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 16 Oct 2020 14:25:40 +0200 Subject: [PATCH 0001/2632] resources#relativePath does not honour path casing strategy --- src/vs/base/common/resources.ts | 20 ++++++++++++++----- src/vs/base/test/common/resources.test.ts | 2 ++ src/vs/platform/workspace/common/workspace.ts | 10 ++++++---- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts index 621c97368c75..88cbade64115 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -8,7 +8,7 @@ import * as paths from 'vs/base/common/path'; import { URI, uriToFsPath } from 'vs/base/common/uri'; import { equalsIgnoreCase, compare as strCompare } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; -import { isWindows, isLinux } from 'vs/base/common/platform'; +import { isLinux } from 'vs/base/common/platform'; import { CharCode } from 'vs/base/common/charCode'; import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob'; import { TernarySearchTree } from 'vs/base/common/map'; @@ -229,11 +229,10 @@ export class ExtUri implements IExtUri { if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) { return undefined; } - if (from.scheme === Schemas.file) { - const relativePath = paths.relative(originalFSPath(from), originalFSPath(to)); - return isWindows ? extpath.toSlashes(relativePath) : relativePath; - } let fromPath = from.path || '/', toPath = to.path || '/'; + if (getWindowsDriveLetter(fromPath) !== getWindowsDriveLetter(toPath)) { + return undefined; // no relative path possible if the drive letter doesn't match + } if (this._ignorePathCasing(from)) { // make casing of fromPath match toPath let i = 0; @@ -393,6 +392,17 @@ export function distinctParents(items: T[], resourceAccessor: (item: T) => UR return distinctParents; } +/** + * Given a URI path (not a fs path!), tests if the path looks like a Window path with drive letter and returns the lowercase variant of that drive letter. + * @param path returns the drive letter (lower case) or undefined if the path does not look like a windows path + */ +function getWindowsDriveLetter(path: string): string | undefined { + if (/^\/[a-zA-Z]:(\/|$)/.test(path)) { + return path.charAt(1).toLowerCase(); + } + return undefined; +} + /** * Data URI related helpers. */ diff --git a/src/vs/base/test/common/resources.test.ts b/src/vs/base/test/common/resources.test.ts index 8026e240bba4..c608e113a3d8 100644 --- a/src/vs/base/test/common/resources.test.ts +++ b/src/vs/base/test/common/resources.test.ts @@ -273,6 +273,8 @@ suite('Resources', () => { assertRelativePath(URI.parse('foo://a/foo'), URI.parse('foo://A/FOO/BAR/GOO'), 'BAR/GOO', false, true); assertRelativePath(URI.parse('foo://a/foo/xoo'), URI.parse('foo://A/FOO/BAR/GOO'), '../BAR/GOO', false, true); assertRelativePath(URI.parse('foo:///c:/a/foo'), URI.parse('foo:///C:/a/foo/xoo/'), 'xoo', false, true); + assertRelativePath(URI.parse('foo:///c:/a/foo'), URI.parse('foo:///D:/a/foo/xoo/'), undefined, false, true); + assertRelativePath(URI.parse('file:///c:/a/foo'), URI.parse('file:///D:/a/foo/xoo/'), undefined, false, true); if (isWindows) { assertRelativePath(URI.file('c:\\foo\\bar'), URI.file('c:\\foo\\bar'), ''); diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index c0c34d74f572..3e7183ea6d4c 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -236,12 +236,14 @@ export function toWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[], let result: WorkspaceFolder[] = []; let seen: Set = new Set(); - const relativeTo = resources.dirname(workspaceConfigFile); + const extUri = resources.extUriBiasedIgnorePathCase; + + const relativeTo = extUri.dirname(workspaceConfigFile); for (let configuredFolder of configuredFolders) { let uri: URI | null = null; if (isRawFileWorkspaceFolder(configuredFolder)) { if (configuredFolder.path) { - uri = resources.resolvePath(relativeTo, configuredFolder.path); + uri = extUri.resolvePath(relativeTo, configuredFolder.path); } } else if (isRawUriWorkspaceFolder(configuredFolder)) { try { @@ -257,11 +259,11 @@ export function toWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[], } if (uri) { // remove duplicates - let comparisonKey = resources.getComparisonKey(uri); + let comparisonKey = extUri.getComparisonKey(uri); if (!seen.has(comparisonKey)) { seen.add(comparisonKey); - const name = configuredFolder.name || resources.basenameOrAuthority(uri); + const name = configuredFolder.name || extUri.basenameOrAuthority(uri); result.push(new WorkspaceFolder({ uri, name, index: result.length }, configuredFolder)); } } From da712f4071300472ce9dcac45ab6da524ecfade4 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 16 Oct 2020 16:30:41 +0200 Subject: [PATCH 0002/2632] more changes --- src/vs/platform/workspace/common/workspace.ts | 2 +- src/vs/platform/workspaces/common/workspaces.ts | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index 3e7183ea6d4c..d79c047f066b 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -236,7 +236,7 @@ export function toWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[], let result: WorkspaceFolder[] = []; let seen: Set = new Set(); - const extUri = resources.extUriBiasedIgnorePathCase; + const extUri = resources.extUriBiasedIgnorePathCase; // To be replaced by the UriIdentityService as parameter: #108793 const relativeTo = extUri.dirname(workspaceConfigFile); for (let configuredFolder of configuredFolders) { diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index d6643801ab6c..a4837165c061 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -9,7 +9,7 @@ import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/works import { URI, UriComponents } from 'vs/base/common/uri'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; import { extname, isAbsolute } from 'vs/base/common/path'; -import { dirname, resolvePath, isEqualAuthority, relativePath, extname as resourceExtname, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; +import { isEqualAuthority, extname as resourceExtname, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import * as jsonEdit from 'vs/base/common/jsonEdit'; import * as json from 'vs/base/common/json'; import { Schemas } from 'vs/base/common/network'; @@ -217,7 +217,9 @@ export function getStoredWorkspaceFolder(folderURI: URI, forceAbsolute: boolean, return { name: folderName, uri: folderURI.toString(true) }; } - let folderPath = !forceAbsolute ? relativePath(targetConfigFolderURI, folderURI) : undefined; + const extUri = extUriBiasedIgnorePathCase; // To be replaced by the UriIdentityService as parameter: #108793 + + let folderPath = !forceAbsolute ? extUri.relativePath(targetConfigFolderURI, folderURI) : undefined; if (folderPath !== undefined) { if (folderPath.length === 0) { folderPath = '.'; @@ -258,14 +260,16 @@ export function getStoredWorkspaceFolder(folderURI: URI, forceAbsolute: boolean, export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string, configPathURI: URI, isFromUntitledWorkspace: boolean, targetConfigPathURI: URI) { let storedWorkspace = doParseStoredWorkspace(configPathURI, rawWorkspaceContents); - const sourceConfigFolder = dirname(configPathURI); - const targetConfigFolder = dirname(targetConfigPathURI); + const extUri = extUriBiasedIgnorePathCase; // To be replaced by the UriIdentityService as parameter: #108793 + + const sourceConfigFolder = extUri.dirname(configPathURI); + const targetConfigFolder = extUri.dirname(targetConfigPathURI); const rewrittenFolders: IStoredWorkspaceFolder[] = []; const slashForPath = useSlashForPath(storedWorkspace.folders); for (const folder of storedWorkspace.folders) { - const folderURI = isRawFileWorkspaceFolder(folder) ? resolvePath(sourceConfigFolder, folder.path) : URI.parse(folder.uri); + const folderURI = isRawFileWorkspaceFolder(folder) ? extUri.resolvePath(sourceConfigFolder, folder.path) : URI.parse(folder.uri); let absolute; if (isFromUntitledWorkspace) { // if it was an untitled workspace, try to make paths relative From 8308a6dc10eb29477dc23b8b8e9d8c5074903cc0 Mon Sep 17 00:00:00 2001 From: Orta Date: Fri, 16 Apr 2021 22:27:19 +0100 Subject: [PATCH 0003/2632] Adds the ability to resolve @types and JSDoc imports in inline --- .../server/src/modes/javascriptMode.ts | 128 +++++++++++++----- .../server/src/test/completions.test.ts | 24 +++- .../src/test/jsdocImportFixtures/index.html | 1 + .../src/test/jsdocImportFixtures/index.js | 4 + .../test/jsdocImportFixtures/jsDocTypes.ts | 9 ++ 5 files changed, 125 insertions(+), 41 deletions(-) create mode 100644 extensions/html-language-features/server/src/test/jsdocImportFixtures/index.html create mode 100644 extensions/html-language-features/server/src/test/jsdocImportFixtures/index.js create mode 100644 extensions/html-language-features/server/src/test/jsdocImportFixtures/jsDocTypes.ts diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index 01e0b381d112..ea51fa0e4b21 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -12,20 +12,34 @@ import { } from './languageModes'; import { getWordAtText, isWhitespaceOnly, repeat } from '../utils/strings'; import { HTMLDocumentRegions } from './embeddedSupport'; +import { normalize, sep } from 'path'; import * as ts from 'typescript'; import { getSemanticTokens, getSemanticTokenLegend } from './javascriptSemanticTokens'; const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g; +/** TypeScript does not handle schemes on file references, so normalize and remove the schemes when communicating with tsserver */ +function deschemeURI(uri: string) { + if (!uri.startsWith('file://')) { + return uri ; + } + // This is replicating the logic in TypeScriptServiceClient.normalizedPath + const newPath = normalize(uri.replace('file://', '')); + + // Both \ and / must be escaped in regular expressions + return newPath.replace(new RegExp('\\' + sep, 'g'), '/'); +} + function getLanguageServiceHost(scriptKind: ts.ScriptKind) { const compilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es6.d.ts'], target: ts.ScriptTarget.Latest, moduleResolution: ts.ModuleResolutionKind.Classic, experimentalDecorators: false }; let currentTextDocument = TextDocument.create('init', 'javascript', 1, ''); + let currentWorkspace: Workspace = undefined!; const jsLanguageService = import(/* webpackChunkName: "javascriptLibs" */ './javascriptLibs').then(libs => { const host: ts.LanguageServiceHost = { getCompilationSettings: () => compilerOptions, - getScriptFileNames: () => [currentTextDocument.uri, 'jquery'], + getScriptFileNames: () => [deschemeURI(currentTextDocument.uri), 'jquery'], getScriptKind: (fileName) => { if (fileName === currentTextDocument.uri) { return scriptKind; @@ -33,15 +47,17 @@ function getLanguageServiceHost(scriptKind: ts.ScriptKind) { return fileName.substr(fileName.length - 2) === 'ts' ? ts.ScriptKind.TS : ts.ScriptKind.JS; }, getScriptVersion: (fileName: string) => { - if (fileName === currentTextDocument.uri) { + if (fileName === deschemeURI(currentTextDocument.uri)) { return String(currentTextDocument.version); } return '1'; // default lib an jquery.d.ts are static }, getScriptSnapshot: (fileName: string) => { let text = ''; - if (fileName === currentTextDocument.uri) { + if (fileName === deschemeURI(currentTextDocument.uri)) { text = currentTextDocument.getText(); + } else if (ts.sys.fileExists(fileName)) { + text = ts.sys.readFile(fileName, 'utf8')!; } else { text = libs.loadLibrary(fileName); } @@ -51,14 +67,24 @@ function getLanguageServiceHost(scriptKind: ts.ScriptKind) { getChangeRange: () => undefined }; }, - getCurrentDirectory: () => '', - getDefaultLibFileName: (_options: ts.CompilerOptions) => 'es6' + getCurrentDirectory: () => { + const workspace = currentWorkspace && currentWorkspace.folders.find(ws => deschemeURI(currentTextDocument.uri).startsWith(deschemeURI(ws.uri))); + return workspace ? deschemeURI(workspace.uri) : ''; + }, + getDefaultLibFileName: (_options: ts.CompilerOptions) => 'es6', + fileExists: ts.sys.fileExists, + readFile: ts.sys.readFile, + readDirectory: ts.sys.readDirectory, + directoryExists: ts.sys.directoryExists, + getDirectories: ts.sys.getDirectories, }; + return ts.createLanguageService(host); }); return { - async getLanguageService(jsDocument: TextDocument): Promise { + async getLanguageService(jsDocument: TextDocument, workspace: Workspace): Promise { currentTextDocument = jsDocument; + currentWorkspace = workspace; return jsLanguageService; }, getCompilationSettings() { @@ -84,9 +110,11 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { host.getCompilationSettings()['experimentalDecorators'] = settings && settings.javascript && settings.javascript.implicitProjectConfig.experimentalDecorators; const jsDocument = jsDocuments.get(document); - const languageService = await host.getLanguageService(jsDocument); - const syntaxDiagnostics: ts.Diagnostic[] = languageService.getSyntacticDiagnostics(jsDocument.uri); - const semanticDiagnostics = languageService.getSemanticDiagnostics(jsDocument.uri); + const languageService = await host.getLanguageService(jsDocument, workspace); + const filePath = deschemeURI(jsDocument.uri); + + const syntaxDiagnostics: ts.Diagnostic[] = languageService.getSyntacticDiagnostics(filePath); + const semanticDiagnostics = languageService.getSemanticDiagnostics(filePath); return syntaxDiagnostics.concat(semanticDiagnostics).map((diag: ts.Diagnostic): Diagnostic => { return { range: convertRange(jsDocument, diag), @@ -98,9 +126,12 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { const jsDocument = jsDocuments.get(document); - const jsLanguageService = await host.getLanguageService(jsDocument); + const jsLanguageService = await host.getLanguageService(jsDocument, workspace); + const filePath = deschemeURI(jsDocument.uri); + let offset = jsDocument.offsetAt(position); - let completions = jsLanguageService.getCompletionsAtPosition(jsDocument.uri, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false }); + let completions = jsLanguageService.getCompletionsAtPosition(filePath, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false }); + if (!completions) { return { isIncomplete: false, items: [] }; } @@ -126,9 +157,11 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { const jsDocument = jsDocuments.get(document); - const jsLanguageService = await host.getLanguageService(jsDocument); + const jsLanguageService = await host.getLanguageService(jsDocument, workspace); + const filePath = deschemeURI(jsDocument.uri); + // @ts-expect-error until 4.3 protocol update - let details = jsLanguageService.getCompletionEntryDetails(jsDocument.uri, item.data.offset, item.label, undefined, undefined, undefined, undefined); + let details = jsLanguageService.getCompletionEntryDetails(filePath, item.data.offset, item.label, undefined, undefined, undefined, undefined); if (details) { item.detail = ts.displayPartsToString(details.displayParts); item.documentation = ts.displayPartsToString(details.documentation); @@ -138,8 +171,10 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { const jsDocument = jsDocuments.get(document); - const jsLanguageService = await host.getLanguageService(jsDocument); - let info = jsLanguageService.getQuickInfoAtPosition(jsDocument.uri, jsDocument.offsetAt(position)); + const jsLanguageService = await host.getLanguageService(jsDocument, workspace); + const filePath = deschemeURI(jsDocument.uri); + + let info = jsLanguageService.getQuickInfoAtPosition(filePath, jsDocument.offsetAt(position)); if (info) { const contents = ts.displayPartsToString(info.displayParts); return { @@ -151,8 +186,10 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { const jsDocument = jsDocuments.get(document); - const jsLanguageService = await host.getLanguageService(jsDocument); - let signHelp = jsLanguageService.getSignatureHelpItems(jsDocument.uri, jsDocument.offsetAt(position), undefined); + const jsLanguageService = await host.getLanguageService(jsDocument, workspace); + const filePath = deschemeURI(jsDocument.uri); + + let signHelp = jsLanguageService.getSignatureHelpItems(filePath, jsDocument.offsetAt(position), undefined); if (signHelp) { let ret: SignatureHelp = { activeSignature: signHelp.selectedItemIndex, @@ -189,13 +226,15 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { @@ -211,8 +250,10 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { const jsDocument = jsDocuments.get(document); - const jsLanguageService = await host.getLanguageService(jsDocument); - const highlights = jsLanguageService.getDocumentHighlights(jsDocument.uri, jsDocument.offsetAt(position), [jsDocument.uri]); + const jsLanguageService = await host.getLanguageService(jsDocument, workspace); + const filePath = deschemeURI(jsDocument.uri); + + const highlights = jsLanguageService.getDocumentHighlights(filePath, jsDocument.offsetAt(position), [filePath]); const out: DocumentHighlight[] = []; for (const entry of highlights || []) { for (const highlight of entry.highlightSpans) { @@ -226,8 +267,10 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { const jsDocument = jsDocuments.get(document); - const jsLanguageService = await host.getLanguageService(jsDocument); - let items = jsLanguageService.getNavigationBarItems(jsDocument.uri); + const jsLanguageService = await host.getLanguageService(jsDocument, workspace); + const filePath = deschemeURI(jsDocument.uri); + + let items = jsLanguageService.getNavigationBarItems(filePath); if (items) { let result: SymbolInformation[] = []; let existing = Object.create(null); @@ -263,8 +306,10 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { const jsDocument = jsDocuments.get(document); - const jsLanguageService = await host.getLanguageService(jsDocument); - let definition = jsLanguageService.getDefinitionAtPosition(jsDocument.uri, jsDocument.offsetAt(position)); + const jsLanguageService = await host.getLanguageService(jsDocument, workspace); + const filePath = deschemeURI(jsDocument.uri); + + let definition = jsLanguageService.getDefinitionAtPosition(filePath, jsDocument.offsetAt(position)); if (definition) { return definition.filter(d => d.fileName === jsDocument.uri).map(d => { return { @@ -277,10 +322,12 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { const jsDocument = jsDocuments.get(document); - const jsLanguageService = await host.getLanguageService(jsDocument); - let references = jsLanguageService.getReferencesAtPosition(jsDocument.uri, jsDocument.offsetAt(position)); + const jsLanguageService = await host.getLanguageService(jsDocument, workspace); + const filePath = deschemeURI(jsDocument.uri); + + let references = jsLanguageService.getReferencesAtPosition(filePath, jsDocument.offsetAt(position)); if (references) { - return references.filter(d => d.fileName === jsDocument.uri).map(d => { + return references.filter(d => d.fileName === filePath).map(d => { return { uri: document.uri, range: convertRange(jsDocument, d.textSpan) @@ -291,17 +338,20 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { const jsDocument = jsDocuments.get(document); - const jsLanguageService = await host.getLanguageService(jsDocument); + const jsLanguageService = await host.getLanguageService(jsDocument, workspace); + const filePath = deschemeURI(jsDocument.uri); + function convertSelectionRange(selectionRange: ts.SelectionRange): SelectionRange { const parent = selectionRange.parent ? convertSelectionRange(selectionRange.parent) : undefined; return SelectionRange.create(convertRange(jsDocument, selectionRange.textSpan), parent); } - const range = jsLanguageService.getSmartSelectionRange(jsDocument.uri, jsDocument.offsetAt(position)); + const range = jsLanguageService.getSmartSelectionRange(filePath, jsDocument.offsetAt(position)); return convertSelectionRange(range); }, async format(document: TextDocument, range: Range, formatParams: FormattingOptions, settings: Settings = globalSettings): Promise { const jsDocument = documentRegions.get(document).getEmbeddedDocument('javascript', true); - const jsLanguageService = await host.getLanguageService(jsDocument); + const jsLanguageService = await host.getLanguageService(jsDocument, workspace); + const filePath = deschemeURI(jsDocument.uri); let formatterSettings = settings && settings.javascript && settings.javascript.format; @@ -314,7 +364,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { const jsDocument = jsDocuments.get(document); - const jsLanguageService = await host.getLanguageService(jsDocument); - let spans = jsLanguageService.getOutliningSpans(jsDocument.uri); + const jsLanguageService = await host.getLanguageService(jsDocument, workspace); + const filePath = deschemeURI(jsDocument.uri); + + let spans = jsLanguageService.getOutliningSpans(filePath); let ranges: FoldingRange[] = []; for (let span of spans) { let curr = convertRange(jsDocument, span.textSpan); @@ -360,8 +412,10 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { const jsDocument = jsDocuments.get(document); - const jsLanguageService = await host.getLanguageService(jsDocument); - return getSemanticTokens(jsLanguageService, jsDocument, jsDocument.uri); + const jsLanguageService = await host.getLanguageService(jsDocument, workspace); + const filePath = deschemeURI(jsDocument.uri); + + return getSemanticTokens(jsLanguageService, jsDocument, filePath); }, getSemanticTokenLegend(): { types: string[], modifiers: string[] } { return getSemanticTokenLegend(); diff --git a/extensions/html-language-features/server/src/test/completions.test.ts b/extensions/html-language-features/server/src/test/completions.test.ts index 0371a2f74cd0..f0d18af2df70 100644 --- a/extensions/html-language-features/server/src/test/completions.test.ts +++ b/extensions/html-language-features/server/src/test/completions.test.ts @@ -94,11 +94,27 @@ suite('HTML Completion', () => { }); }); +const triggerSuggestCommand = { + title: 'Suggest', + command: 'editor.action.triggerSuggest' +}; + +suite('JSDoc Imports', () => { + const fixtureRoot = path.resolve(__dirname, '../../src/test/jsdocImportFixtures'); + const fixtureWorkspace = { name: 'fixture', uri: URI.file(fixtureRoot).toString() }; + const indexHtmlUri = URI.file(path.resolve(fixtureRoot, 'index.html')).toString(); + + test('Imports across files', async () => { + await testCompletionFor('', { + items: [ + { label: 'other', }, + { label: 'property', }, + ] + }, indexHtmlUri, [fixtureWorkspace] ); + }); +}); + suite('HTML Path Completion', () => { - const triggerSuggestCommand = { - title: 'Suggest', - command: 'editor.action.triggerSuggest' - }; const fixtureRoot = path.resolve(__dirname, '../../src/test/pathCompletionFixtures'); const fixtureWorkspace = { name: 'fixture', uri: URI.file(fixtureRoot).toString() }; diff --git a/extensions/html-language-features/server/src/test/jsdocImportFixtures/index.html b/extensions/html-language-features/server/src/test/jsdocImportFixtures/index.html new file mode 100644 index 000000000000..c5bd93d63657 --- /dev/null +++ b/extensions/html-language-features/server/src/test/jsdocImportFixtures/index.html @@ -0,0 +1 @@ + `; } From f9c927cf7a29a59b896b6cdac2d8b5d2d43afea5 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 30 Jan 2025 15:36:53 -0800 Subject: [PATCH 0226/2632] eng: rev to 1.98 (#239269) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4963a5eb7444..9f29e0386f33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "code-oss-dev", - "version": "1.97.0", + "version": "1.98.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "code-oss-dev", - "version": "1.97.0", + "version": "1.98.0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 53c91131ccf9..b45b2fe3a699 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "code-oss-dev", - "version": "1.97.0", + "version": "1.98.0", "distro": "4e8c47ac95d95aa744f78305db0f163dcd407126", "author": { "name": "Microsoft Corporation" From f2b55975952542f9c4b92cedd42153c62a9bc4cd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 31 Jan 2025 09:18:46 +0100 Subject: [PATCH 0227/2632] Ben/probable-bobcat (#239286) * Should not change values in environmentService (fix #171707) * dialogs - remove `closeOnLinkClick` option Closing a dialog from anything but a button click is rather unexpected and there is usage of this anymore. * Code duplication for text input actions (fix #239187) --- src/vs/base/browser/ui/dialog/dialog.ts | 15 ---- src/vs/platform/dialogs/common/dialogs.ts | 1 - .../environment/common/environmentService.ts | 2 +- .../browser/actions/textInputActions.ts | 86 +++++++++---------- .../browser/parts/dialogs/dialogHandler.ts | 1 - .../find/browser/terminalFindWidget.ts | 13 ++- .../find/browser/textInputContextMenu.ts | 63 -------------- .../environment/browser/environmentService.ts | 3 +- 8 files changed, 57 insertions(+), 127 deletions(-) delete mode 100644 src/vs/workbench/contrib/terminalContrib/find/browser/textInputContextMenu.ts diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 2ed8d24fadf7..a26806c6e435 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -37,7 +37,6 @@ export interface IDialogOptions { readonly icon?: ThemeIcon; readonly buttonDetails?: string[]; readonly disableCloseAction?: boolean; - readonly closeOnLinkClick?: boolean; readonly disableDefaultAction?: boolean; readonly buttonStyles: IButtonStyles; readonly checkboxStyles: ICheckboxStyles; @@ -212,20 +211,6 @@ export class Dialog extends Disposable { return; }; - if (this.options.closeOnLinkClick) { - for (const el of this.messageContainer.getElementsByTagName('a')) { - this._register(addDisposableListener(el, EventType.CLICK, () => { - setTimeout(close); // HACK to ensure the link action is triggered before the dialog is closed - })); - this._register(addDisposableListener(el, EventType.KEY_DOWN, (e: KeyboardEvent) => { - const evt = new StandardKeyboardEvent(e); - if (evt.equals(KeyCode.Enter)) { - setTimeout(close); // HACK to ensure the link action is triggered before the dialog is closed - } - })); - } - } - const buttonBar = this.buttonBar = this._register(new ButtonBar(this.buttonsContainer)); const buttonMap = this.rearrangeButtons(this.buttons, this.options.cancelId); diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 96259d6807ef..dc1c785356e8 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -278,7 +278,6 @@ export interface ICustomDialogOptions { readonly classes?: string[]; readonly icon?: ThemeIcon; readonly disableCloseAction?: boolean; - readonly closeOnLinkClick?: boolean; } export interface ICustomDialogMarkdown { diff --git a/src/vs/platform/environment/common/environmentService.ts b/src/vs/platform/environment/common/environmentService.ts index 1984abb76c8d..7004dd4252bf 100644 --- a/src/vs/platform/environment/common/environmentService.ts +++ b/src/vs/platform/environment/common/environmentService.ts @@ -252,7 +252,7 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron return undefined; } - editSessionId: string | undefined = this.args['editSessionId']; + get editSessionId(): string | undefined { return this.args['editSessionId']; } get continueOn(): string | undefined { return this.args['continueOn']; diff --git a/src/vs/workbench/browser/actions/textInputActions.ts b/src/vs/workbench/browser/actions/textInputActions.ts index 20b277dee3c0..0c0d7d1386fd 100644 --- a/src/vs/workbench/browser/actions/textInputActions.ts +++ b/src/vs/workbench/browser/actions/textInputActions.ts @@ -16,11 +16,53 @@ import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js'; import { Event as BaseEvent } from '../../../base/common/event.js'; import { Lazy } from '../../../base/common/lazy.js'; +export function createTextInputActions(clipboardService: IClipboardService): IAction[] { + return [ + + // Undo/Redo + new Action('undo', localize('undo', "Undo"), undefined, true, async () => getActiveDocument().execCommand('undo')), + new Action('redo', localize('redo', "Redo"), undefined, true, async () => getActiveDocument().execCommand('redo')), + new Separator(), + + // Cut / Copy / Paste + new Action('editor.action.clipboardCutAction', localize('cut', "Cut"), undefined, true, async () => getActiveDocument().execCommand('cut')), + new Action('editor.action.clipboardCopyAction', localize('copy', "Copy"), undefined, true, async () => getActiveDocument().execCommand('copy')), + new Action('editor.action.clipboardPasteAction', localize('paste', "Paste"), undefined, true, async element => { + + // Native: paste is supported + if (isNative) { + getActiveDocument().execCommand('paste'); + } + + // Web: paste is not supported due to security reasons + else { + const clipboardText = await clipboardService.readText(); + if ( + isHTMLTextAreaElement(element) || + isHTMLInputElement(element) + ) { + const selectionStart = element.selectionStart || 0; + const selectionEnd = element.selectionEnd || 0; + + element.value = `${element.value.substring(0, selectionStart)}${clipboardText}${element.value.substring(selectionEnd, element.value.length)}`; + element.selectionStart = selectionStart + clipboardText.length; + element.selectionEnd = element.selectionStart; + element.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); + } + } + }), + new Separator(), + + // Select All + new Action('editor.action.selectAll', localize('selectAll', "Select All"), undefined, true, async () => getActiveDocument().execCommand('selectAll')) + ]; +} + export class TextInputActionsProvider extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.textInputActionsProvider'; - private readonly textInputActions = new Lazy(() => this.createActions()); + private readonly textInputActions = new Lazy(() => createTextInputActions(this.clipboardService)); constructor( @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @@ -32,48 +74,6 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo this.registerListeners(); } - private createActions(): IAction[] { - return [ - - // Undo/Redo - new Action('undo', localize('undo', "Undo"), undefined, true, async () => getActiveDocument().execCommand('undo')), - new Action('redo', localize('redo', "Redo"), undefined, true, async () => getActiveDocument().execCommand('redo')), - new Separator(), - - // Cut / Copy / Paste - new Action('editor.action.clipboardCutAction', localize('cut', "Cut"), undefined, true, async () => getActiveDocument().execCommand('cut')), - new Action('editor.action.clipboardCopyAction', localize('copy', "Copy"), undefined, true, async () => getActiveDocument().execCommand('copy')), - new Action('editor.action.clipboardPasteAction', localize('paste', "Paste"), undefined, true, async element => { - - // Native: paste is supported - if (isNative) { - getActiveDocument().execCommand('paste'); - } - - // Web: paste is not supported due to security reasons - else { - const clipboardText = await this.clipboardService.readText(); - if ( - isHTMLTextAreaElement(element) || - isHTMLInputElement(element) - ) { - const selectionStart = element.selectionStart || 0; - const selectionEnd = element.selectionEnd || 0; - - element.value = `${element.value.substring(0, selectionStart)}${clipboardText}${element.value.substring(selectionEnd, element.value.length)}`; - element.selectionStart = selectionStart + clipboardText.length; - element.selectionEnd = element.selectionStart; - element.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); - } - } - }), - new Separator(), - - // Select All - new Action('editor.action.selectAll', localize('selectAll', "Select All"), undefined, true, async () => getActiveDocument().execCommand('selectAll')) - ]; - } - private registerListeners(): void { // Context menu support in input/textarea diff --git a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index b80950387c84..21f62b646c29 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -137,7 +137,6 @@ export class BrowserDialogHandler extends AbstractDialogHandler { renderBody, icon: customOptions?.icon, disableCloseAction: customOptions?.disableCloseAction, - closeOnLinkClick: customOptions?.closeOnLinkClick, buttonDetails: customOptions?.buttonDetails, checkboxLabel: checkbox?.label, checkboxChecked: checkbox?.checked, diff --git a/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts b/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts index ba91e210b468..9c079280d62e 100644 --- a/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts @@ -15,11 +15,12 @@ import { IKeybindingService } from '../../../../../platform/keybinding/common/ke import { Event } from '../../../../../base/common/event.js'; import type { ISearchOptions } from '@xterm/addon-search'; import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js'; -import { openContextMenu } from './textInputContextMenu.js'; import { IDisposable } from '../../../../../base/common/lifecycle.js'; import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; import { TerminalFindCommandId } from '../common/terminal.find.js'; import { TerminalClipboardContribution } from '../../clipboard/browser/terminal.clipboard.contribution.js'; +import { StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js'; +import { createTextInputActions } from '../../../../browser/actions/textInputActions.js'; const TERMINAL_FIND_WIDGET_INITIAL_WIDTH = 419; @@ -74,7 +75,15 @@ export class TerminalFindWidget extends SimpleFindWidget { } const findInputDomNode = this.getFindInputDomNode(); this._register(dom.addDisposableListener(findInputDomNode, 'contextmenu', (event) => { - openContextMenu(dom.getWindow(findInputDomNode), event, clipboardService, contextMenuService); + const targetWindow = dom.getWindow(findInputDomNode); + const standardEvent = new StandardMouseEvent(targetWindow, event); + const actions = createTextInputActions(clipboardService); + + contextMenuService.showContextMenu({ + getAnchor: () => standardEvent, + getActions: () => actions, + getActionsContext: () => event.target, + }); event.stopPropagation(); })); this._register(themeService.onDidColorThemeChange(() => { diff --git a/src/vs/workbench/contrib/terminalContrib/find/browser/textInputContextMenu.ts b/src/vs/workbench/contrib/terminalContrib/find/browser/textInputContextMenu.ts deleted file mode 100644 index d0ba875c3a74..000000000000 --- a/src/vs/workbench/contrib/terminalContrib/find/browser/textInputContextMenu.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { getActiveWindow, isHTMLInputElement, isHTMLTextAreaElement } from '../../../../../base/browser/dom.js'; -import { StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js'; -import { Action, IAction, Separator } from '../../../../../base/common/actions.js'; -import { isNative } from '../../../../../base/common/platform.js'; -import { localize } from '../../../../../nls.js'; -import { IClipboardService } from '../../../../../platform/clipboard/common/clipboardService.js'; -import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js'; - -export function openContextMenu(targetWindow: Window, event: MouseEvent, clipboardService: IClipboardService, contextMenuService: IContextMenuService): void { - const standardEvent = new StandardMouseEvent(targetWindow, event); - - // Actions from workbench/browser/actions/textInputActions - const actions: IAction[] = []; - actions.push( - - // Undo/Redo - new Action('undo', localize('undo', "Undo"), undefined, true, async () => getActiveWindow().document.execCommand('undo')), - new Action('redo', localize('redo', "Redo"), undefined, true, async () => getActiveWindow().document.execCommand('redo')), - new Separator(), - - // Cut / Copy / Paste - new Action('editor.action.clipboardCutAction', localize('cut', "Cut"), undefined, true, async () => getActiveWindow().document.execCommand('cut')), - new Action('editor.action.clipboardCopyAction', localize('copy', "Copy"), undefined, true, async () => getActiveWindow().document.execCommand('copy')), - new Action('editor.action.clipboardPasteAction', localize('paste', "Paste"), undefined, true, async element => { - - // Native: paste is supported - if (isNative) { - getActiveWindow().document.execCommand('paste'); - } - - // Web: paste is not supported due to security reasons - else { - const clipboardText = await clipboardService.readText(); - if ( - isHTMLTextAreaElement(element) || - isHTMLInputElement(element) - ) { - const selectionStart = element.selectionStart || 0; - const selectionEnd = element.selectionEnd || 0; - - element.value = `${element.value.substring(0, selectionStart)}${clipboardText}${element.value.substring(selectionEnd, element.value.length)}`; - element.selectionStart = selectionStart + clipboardText.length; - element.selectionEnd = element.selectionStart; - } - } - }), - new Separator(), - - // Select All - new Action('editor.action.selectAll', localize('selectAll', "Select All"), undefined, true, async () => getActiveWindow().document.execCommand('selectAll')) - ); - - contextMenuService.showContextMenu({ - getAnchor: () => standardEvent, - getActions: () => actions, - getActionsContext: () => event.target, - }); -} diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 2ac401d7357b..03cfdb54aa88 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -259,7 +259,8 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi @memoize get profile(): string | undefined { return this.payload?.get('profile'); } - editSessionId: string | undefined = this.options.editSessionId; + @memoize + get editSessionId(): string | undefined { return this.options.editSessionId; } private payload: Map | undefined; From 9df979cc9a6acf6678c0ccb680f08cb29f567be0 Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 31 Jan 2025 18:27:39 +0900 Subject: [PATCH 0228/2632] ci: fix installation of build dependencies (#239290) * ci: fix installation of build dependencies * ci: add missing quality parameter --- build/azure-pipelines/alpine/cli-build-alpine.yml | 8 +++++--- .../darwin/product-build-darwin-cli-sign.yml | 12 ++++++++++++ .../darwin/product-build-darwin-universal.yml | 2 ++ build/azure-pipelines/linux/cli-build-linux.yml | 2 ++ .../linux/product-build-linux-legacy-server.yml | 2 ++ build/azure-pipelines/linux/product-build-linux.yml | 2 ++ build/azure-pipelines/product-build.yml | 2 ++ build/azure-pipelines/product-publish.yml | 2 ++ .../win32/product-build-win32-cli-sign.yml | 12 ++++++++++++ 9 files changed, 41 insertions(+), 3 deletions(-) diff --git a/build/azure-pipelines/alpine/cli-build-alpine.yml b/build/azure-pipelines/alpine/cli-build-alpine.yml index 07321ebcd97b..145133481f26 100644 --- a/build/azure-pipelines/alpine/cli-build-alpine.yml +++ b/build/azure-pipelines/alpine/cli-build-alpine.yml @@ -19,13 +19,15 @@ steps: nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - template: ../cli/cli-apply-patches.yml@self + - script: | set -e npm ci workingDirectory: build - displayName: Install pipeline build - - - template: ../cli/cli-apply-patches.yml@self + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install build dependencies - task: Npm@1 displayName: Download openssl prebuilt diff --git a/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml b/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml index 32615c584637..b3d01ca7ff16 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml @@ -3,6 +3,8 @@ parameters: type: boolean - name: VSCODE_BUILD_MACOS_ARM64 type: boolean + - name: VSCODE_QUALITY + type: string steps: - task: NodeTool@0 @@ -11,6 +13,14 @@ steps: versionFilePath: .nvmrc nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + - script: node build/setup-npm-registry.js $NPM_REGISTRY build condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Registry @@ -43,6 +53,8 @@ steps: echo "Npm install failed $i, trying again..." done workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Install build dependencies - template: ../cli/cli-darwin-sign.yml@self diff --git a/build/azure-pipelines/darwin/product-build-darwin-universal.yml b/build/azure-pipelines/darwin/product-build-darwin-universal.yml index 27408f71432e..3bb62e154034 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-universal.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-universal.yml @@ -46,6 +46,8 @@ steps: echo "Npm install failed $i, trying again..." done workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Install build dependencies - download: current diff --git a/build/azure-pipelines/linux/cli-build-linux.yml b/build/azure-pipelines/linux/cli-build-linux.yml index 89bc8a39e24f..dba949395de3 100644 --- a/build/azure-pipelines/linux/cli-build-linux.yml +++ b/build/azure-pipelines/linux/cli-build-linux.yml @@ -72,6 +72,8 @@ steps: echo "Npm install failed $i, trying again..." done workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Install build dependencies - script: | diff --git a/build/azure-pipelines/linux/product-build-linux-legacy-server.yml b/build/azure-pipelines/linux/product-build-linux-legacy-server.yml index 6e4022b40649..4c26baf2f999 100644 --- a/build/azure-pipelines/linux/product-build-linux-legacy-server.yml +++ b/build/azure-pipelines/linux/product-build-linux-legacy-server.yml @@ -97,6 +97,8 @@ steps: echo "Npm install failed $i, trying again..." done workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Install build dependencies - script: | diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index b87a82b9fb3e..b9300b1ccba0 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -111,6 +111,8 @@ steps: echo "Npm install failed $i, trying again..." done workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Install build dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 39075d822831..ee7dcb99eeab 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -406,6 +406,7 @@ extends: steps: - template: build/azure-pipelines/win32/product-build-win32-cli-sign.yml@self parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} VSCODE_BUILD_WIN32: ${{ parameters.VSCODE_BUILD_WIN32 }} VSCODE_BUILD_WIN32_ARM64: ${{ parameters.VSCODE_BUILD_WIN32_ARM64 }} @@ -706,6 +707,7 @@ extends: steps: - template: build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml@self parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} VSCODE_BUILD_MACOS: ${{ parameters.VSCODE_BUILD_MACOS }} VSCODE_BUILD_MACOS_ARM64: ${{ parameters.VSCODE_BUILD_MACOS_ARM64 }} diff --git a/build/azure-pipelines/product-publish.yml b/build/azure-pipelines/product-publish.yml index c9728a2a113c..8ecf5e6238ef 100644 --- a/build/azure-pipelines/product-publish.yml +++ b/build/azure-pipelines/product-publish.yml @@ -26,6 +26,8 @@ steps: - pwsh: | npm ci workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Install build dependencies - download: current diff --git a/build/azure-pipelines/win32/product-build-win32-cli-sign.yml b/build/azure-pipelines/win32/product-build-win32-cli-sign.yml index 9d9af45d4747..c7f4b0a0a127 100644 --- a/build/azure-pipelines/win32/product-build-win32-cli-sign.yml +++ b/build/azure-pipelines/win32/product-build-win32-cli-sign.yml @@ -3,6 +3,8 @@ parameters: type: boolean - name: VSCODE_BUILD_WIN32_ARM64 type: boolean + - name: VSCODE_QUALITY + type: string steps: - task: NodeTool@0 @@ -12,6 +14,14 @@ steps: versionFilePath: .nvmrc nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + - powershell: node build/setup-npm-registry.js $env:NPM_REGISTRY build condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Registry @@ -39,6 +49,8 @@ steps: $ErrorActionPreference = "Stop" exec { npm ci } workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" retryCountOnTaskFailure: 5 displayName: Install build dependencies From da823db05042dba140c71c12d0d5f3f7a2613914 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 31 Jan 2025 10:35:52 +0100 Subject: [PATCH 0229/2632] Migrate from unsupported paste execCommand in desktop (#239228) (#239293) --- .../browser/actions/textInputActions.ts | 35 ++++++------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/browser/actions/textInputActions.ts b/src/vs/workbench/browser/actions/textInputActions.ts index 0c0d7d1386fd..144544e2032d 100644 --- a/src/vs/workbench/browser/actions/textInputActions.ts +++ b/src/vs/workbench/browser/actions/textInputActions.ts @@ -8,9 +8,8 @@ import { localize } from '../../../nls.js'; import { IWorkbenchLayoutService } from '../../services/layout/browser/layoutService.js'; import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js'; import { Disposable } from '../../../base/common/lifecycle.js'; -import { EventHelper, addDisposableListener, getActiveDocument, getWindow, isHTMLElement, isHTMLInputElement, isHTMLTextAreaElement } from '../../../base/browser/dom.js'; +import { EventHelper, addDisposableListener, getActiveDocument, getWindow, isHTMLInputElement, isHTMLTextAreaElement } from '../../../base/browser/dom.js'; import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../common/contributions.js'; -import { isNative } from '../../../base/common/platform.js'; import { IClipboardService } from '../../../platform/clipboard/common/clipboardService.js'; import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js'; import { Event as BaseEvent } from '../../../base/common/event.js'; @@ -28,27 +27,15 @@ export function createTextInputActions(clipboardService: IClipboardService): IAc new Action('editor.action.clipboardCutAction', localize('cut', "Cut"), undefined, true, async () => getActiveDocument().execCommand('cut')), new Action('editor.action.clipboardCopyAction', localize('copy', "Copy"), undefined, true, async () => getActiveDocument().execCommand('copy')), new Action('editor.action.clipboardPasteAction', localize('paste', "Paste"), undefined, true, async element => { - - // Native: paste is supported - if (isNative) { - getActiveDocument().execCommand('paste'); - } - - // Web: paste is not supported due to security reasons - else { - const clipboardText = await clipboardService.readText(); - if ( - isHTMLTextAreaElement(element) || - isHTMLInputElement(element) - ) { - const selectionStart = element.selectionStart || 0; - const selectionEnd = element.selectionEnd || 0; - - element.value = `${element.value.substring(0, selectionStart)}${clipboardText}${element.value.substring(selectionEnd, element.value.length)}`; - element.selectionStart = selectionStart + clipboardText.length; - element.selectionEnd = element.selectionStart; - element.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); - } + const clipboardText = await clipboardService.readText(); + if (isHTMLTextAreaElement(element) || isHTMLInputElement(element)) { + const selectionStart = element.selectionStart || 0; + const selectionEnd = element.selectionEnd || 0; + + element.value = `${element.value.substring(0, selectionStart)}${clipboardText}${element.value.substring(selectionEnd, element.value.length)}`; + element.selectionStart = selectionStart + clipboardText.length; + element.selectionEnd = element.selectionStart; + element.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); } }), new Separator(), @@ -88,7 +75,7 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo } const target = e.target; - if (!(isHTMLElement(target)) || (target.nodeName.toLowerCase() !== 'input' && target.nodeName.toLowerCase() !== 'textarea')) { + if (!isHTMLTextAreaElement(target) && !isHTMLInputElement(target)) { return; // only for inputs or textareas } From 9d43b0751c91c909eee74ea96f765b1765487d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 31 Jan 2025 10:49:12 +0100 Subject: [PATCH 0230/2632] remove svgz from default file types (#239180) * remove svgz from default file types fixes #231021 * push missing compilation --- build/lib/electron.js | 2 +- build/lib/electron.ts | 2 +- build/win32/code.iss | 8 -------- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/build/lib/electron.js b/build/lib/electron.js index f0eb583f2cba..56992d8a7f71 100644 --- a/build/lib/electron.js +++ b/build/lib/electron.js @@ -195,7 +195,7 @@ exports.config = { 'F# source code': 'fs', 'F# signature file': 'fsi', 'F# script': ['fsx', 'fsscript'], - 'SVG document': ['svg', 'svgz'], + 'SVG document': ['svg'], 'TOML document': 'toml', 'Swift source code': 'swift', }, 'default'), diff --git a/build/lib/electron.ts b/build/lib/electron.ts index 57b27022df86..3bb047dfceeb 100644 --- a/build/lib/electron.ts +++ b/build/lib/electron.ts @@ -176,7 +176,7 @@ export const config = { 'F# source code': 'fs', 'F# signature file': 'fsi', 'F# script': ['fsx', 'fsscript'], - 'SVG document': ['svg', 'svgz'], + 'SVG document': ['svg'], 'TOML document': 'toml', 'Swift source code': 'swift', }, 'default'), diff --git a/build/win32/code.iss b/build/win32/code.iss index a59d7c047f5a..cad6e399f66b 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -1120,14 +1120,6 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svg\D Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svg\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svg\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles -Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.svgz\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles -Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.svgz\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.svgz"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles -Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svgz"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SVGZ}"; Flags: uninsdeletekey; Tasks: associatewithfiles -Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svgz"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles -Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svgz\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles -Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svgz\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles -Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svgz\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles - Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.t\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.t\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.t"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.t"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl}"; Flags: uninsdeletekey; Tasks: associatewithfiles From 1b5d712dda6c12a4f570af5c720e2c11d8661254 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 31 Jan 2025 11:21:51 +0100 Subject: [PATCH 0231/2632] :up: `@parcel/watcher@2.5.1` (#239291) --- extensions/package-lock.json | 112 +++++++++--------- extensions/package.json | 2 +- package-lock.json | 112 +++++++++--------- package.json | 2 +- remote/package-lock.json | 112 +++++++++--------- remote/package.json | 2 +- .../node/watcher/parcel/parcelWatcher.ts | 40 +++++-- .../files/test/node/parcelWatcher.test.ts | 8 +- 8 files changed, 203 insertions(+), 187 deletions(-) diff --git a/extensions/package-lock.json b/extensions/package-lock.json index c27ce93440e7..500d78417624 100644 --- a/extensions/package-lock.json +++ b/extensions/package-lock.json @@ -13,7 +13,7 @@ "typescript": "^5.7.3" }, "devDependencies": { - "@parcel/watcher": "2.5.0", + "@parcel/watcher": "2.5.1", "esbuild": "0.23.0", "vscode-grammar-updater": "^1.1.0" } @@ -403,9 +403,9 @@ } }, "node_modules/@parcel/watcher": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz", - "integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -423,25 +423,25 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.0", - "@parcel/watcher-darwin-arm64": "2.5.0", - "@parcel/watcher-darwin-x64": "2.5.0", - "@parcel/watcher-freebsd-x64": "2.5.0", - "@parcel/watcher-linux-arm-glibc": "2.5.0", - "@parcel/watcher-linux-arm-musl": "2.5.0", - "@parcel/watcher-linux-arm64-glibc": "2.5.0", - "@parcel/watcher-linux-arm64-musl": "2.5.0", - "@parcel/watcher-linux-x64-glibc": "2.5.0", - "@parcel/watcher-linux-x64-musl": "2.5.0", - "@parcel/watcher-win32-arm64": "2.5.0", - "@parcel/watcher-win32-ia32": "2.5.0", - "@parcel/watcher-win32-x64": "2.5.0" + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" } }, "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz", - "integrity": "sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", "cpu": [ "arm64" ], @@ -460,9 +460,9 @@ } }, "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz", - "integrity": "sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", "cpu": [ "arm64" ], @@ -481,9 +481,9 @@ } }, "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz", - "integrity": "sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", "cpu": [ "x64" ], @@ -502,9 +502,9 @@ } }, "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz", - "integrity": "sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", "cpu": [ "x64" ], @@ -523,9 +523,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz", - "integrity": "sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", "cpu": [ "arm" ], @@ -544,9 +544,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz", - "integrity": "sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", "cpu": [ "arm" ], @@ -565,9 +565,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz", - "integrity": "sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", "cpu": [ "arm64" ], @@ -586,9 +586,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz", - "integrity": "sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", "cpu": [ "arm64" ], @@ -607,9 +607,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz", - "integrity": "sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", "cpu": [ "x64" ], @@ -628,9 +628,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz", - "integrity": "sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", "cpu": [ "x64" ], @@ -649,9 +649,9 @@ } }, "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz", - "integrity": "sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", "cpu": [ "arm64" ], @@ -670,9 +670,9 @@ } }, "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz", - "integrity": "sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", "cpu": [ "ia32" ], @@ -691,9 +691,9 @@ } }, "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz", - "integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", "cpu": [ "x64" ], diff --git a/extensions/package.json b/extensions/package.json index 128832816339..f756f6a42f91 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -10,7 +10,7 @@ "postinstall": "node ./postinstall.mjs" }, "devDependencies": { - "@parcel/watcher": "2.5.0", + "@parcel/watcher": "2.5.1", "esbuild": "0.23.0", "vscode-grammar-updater": "^1.1.0" }, diff --git a/package-lock.json b/package-lock.json index 9f29e0386f33..220b5d9879b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@parcel/watcher": "2.5.0", + "@parcel/watcher": "2.5.1", "@types/semver": "^7.5.8", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", @@ -1728,9 +1728,9 @@ } }, "node_modules/@parcel/watcher": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz", - "integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -1747,25 +1747,25 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.0", - "@parcel/watcher-darwin-arm64": "2.5.0", - "@parcel/watcher-darwin-x64": "2.5.0", - "@parcel/watcher-freebsd-x64": "2.5.0", - "@parcel/watcher-linux-arm-glibc": "2.5.0", - "@parcel/watcher-linux-arm-musl": "2.5.0", - "@parcel/watcher-linux-arm64-glibc": "2.5.0", - "@parcel/watcher-linux-arm64-musl": "2.5.0", - "@parcel/watcher-linux-x64-glibc": "2.5.0", - "@parcel/watcher-linux-x64-musl": "2.5.0", - "@parcel/watcher-win32-arm64": "2.5.0", - "@parcel/watcher-win32-ia32": "2.5.0", - "@parcel/watcher-win32-x64": "2.5.0" + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" } }, "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz", - "integrity": "sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", "cpu": [ "arm64" ], @@ -1783,9 +1783,9 @@ } }, "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz", - "integrity": "sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", "cpu": [ "arm64" ], @@ -1803,9 +1803,9 @@ } }, "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz", - "integrity": "sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", "cpu": [ "x64" ], @@ -1823,9 +1823,9 @@ } }, "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz", - "integrity": "sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", "cpu": [ "x64" ], @@ -1843,9 +1843,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz", - "integrity": "sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", "cpu": [ "arm" ], @@ -1863,9 +1863,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz", - "integrity": "sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", "cpu": [ "arm" ], @@ -1883,9 +1883,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz", - "integrity": "sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", "cpu": [ "arm64" ], @@ -1903,9 +1903,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz", - "integrity": "sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", "cpu": [ "arm64" ], @@ -1923,9 +1923,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz", - "integrity": "sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", "cpu": [ "x64" ], @@ -1943,9 +1943,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz", - "integrity": "sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", "cpu": [ "x64" ], @@ -1963,9 +1963,9 @@ } }, "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz", - "integrity": "sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", "cpu": [ "arm64" ], @@ -1983,9 +1983,9 @@ } }, "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz", - "integrity": "sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", "cpu": [ "ia32" ], @@ -2003,9 +2003,9 @@ } }, "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz", - "integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index b45b2fe3a699..3f97b4a3d115 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@parcel/watcher": "2.5.0", + "@parcel/watcher": "2.5.1", "@types/semver": "^7.5.8", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", diff --git a/remote/package-lock.json b/remote/package-lock.json index e16b93eee5ea..787c3c9ba96a 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@parcel/watcher": "2.5.0", + "@parcel/watcher": "2.5.1", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/proxy-agent": "^0.31.0", @@ -89,9 +89,9 @@ "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" }, "node_modules/@parcel/watcher": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.0.tgz", - "integrity": "sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -108,25 +108,25 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.0", - "@parcel/watcher-darwin-arm64": "2.5.0", - "@parcel/watcher-darwin-x64": "2.5.0", - "@parcel/watcher-freebsd-x64": "2.5.0", - "@parcel/watcher-linux-arm-glibc": "2.5.0", - "@parcel/watcher-linux-arm-musl": "2.5.0", - "@parcel/watcher-linux-arm64-glibc": "2.5.0", - "@parcel/watcher-linux-arm64-musl": "2.5.0", - "@parcel/watcher-linux-x64-glibc": "2.5.0", - "@parcel/watcher-linux-x64-musl": "2.5.0", - "@parcel/watcher-win32-arm64": "2.5.0", - "@parcel/watcher-win32-ia32": "2.5.0", - "@parcel/watcher-win32-x64": "2.5.0" + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" } }, "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz", - "integrity": "sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", "cpu": [ "arm64" ], @@ -144,9 +144,9 @@ } }, "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz", - "integrity": "sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", "cpu": [ "arm64" ], @@ -164,9 +164,9 @@ } }, "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz", - "integrity": "sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", "cpu": [ "x64" ], @@ -184,9 +184,9 @@ } }, "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz", - "integrity": "sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", "cpu": [ "x64" ], @@ -204,9 +204,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz", - "integrity": "sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", "cpu": [ "arm" ], @@ -224,9 +224,9 @@ } }, "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz", - "integrity": "sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", "cpu": [ "arm" ], @@ -244,9 +244,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz", - "integrity": "sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", "cpu": [ "arm64" ], @@ -264,9 +264,9 @@ } }, "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz", - "integrity": "sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", "cpu": [ "arm64" ], @@ -284,9 +284,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz", - "integrity": "sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", "cpu": [ "x64" ], @@ -304,9 +304,9 @@ } }, "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz", - "integrity": "sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", "cpu": [ "x64" ], @@ -324,9 +324,9 @@ } }, "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz", - "integrity": "sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", "cpu": [ "arm64" ], @@ -344,9 +344,9 @@ } }, "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz", - "integrity": "sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", "cpu": [ "ia32" ], @@ -364,9 +364,9 @@ } }, "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz", - "integrity": "sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", "cpu": [ "x64" ], diff --git a/remote/package.json b/remote/package.json index b381cd45b106..5cc1ff194680 100644 --- a/remote/package.json +++ b/remote/package.json @@ -5,7 +5,7 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@parcel/watcher": "2.5.0", + "@parcel/watcher": "2.5.1", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/proxy-agent": "^0.31.0", diff --git a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts index c778b998140b..a24ef066a5f7 100644 --- a/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts +++ b/src/vs/platform/files/node/watcher/parcel/parcelWatcher.ts @@ -192,10 +192,16 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS } private registerListeners(): void { + const onUncaughtException = (error: unknown) => this.onUnexpectedError(error); + const onUnhandledRejection = (error: unknown) => this.onUnexpectedError(error); - // Error handling on process - process.on('uncaughtException', error => this.onUnexpectedError(error)); - process.on('unhandledRejection', error => this.onUnexpectedError(error)); + process.on('uncaughtException', onUncaughtException); + process.on('unhandledRejection', onUnhandledRejection); + + this._register(toDisposable(() => { + process.off('uncaughtException', onUncaughtException); + process.off('unhandledRejection', onUnhandledRejection); + })); } protected override async doWatch(requests: IRecursiveWatchRequest[]): Promise { @@ -289,20 +295,24 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS // We already ran before, check for events since const parcelWatcherLib = parcelWatcher; - if (counter > 1) { - const parcelEvents = await parcelWatcherLib.getEventsSince(realPath, snapshotFile, { ignore: this.addPredefinedExcludes(request.excludes), backend: ParcelWatcher.PARCEL_WATCHER_BACKEND }); + try { + if (counter > 1) { + const parcelEvents = await parcelWatcherLib.getEventsSince(realPath, snapshotFile, { ignore: this.addPredefinedExcludes(request.excludes), backend: ParcelWatcher.PARCEL_WATCHER_BACKEND }); - if (cts.token.isCancellationRequested) { - return; + if (cts.token.isCancellationRequested) { + return; + } + + // Handle & emit events + this.onParcelEvents(parcelEvents, watcher, realPathDiffers, realPathLength); } - // Handle & emit events - this.onParcelEvents(parcelEvents, watcher, realPathDiffers, realPathLength); + // Store a snapshot of files to the snapshot file + await parcelWatcherLib.writeSnapshot(realPath, snapshotFile, { ignore: this.addPredefinedExcludes(request.excludes), backend: ParcelWatcher.PARCEL_WATCHER_BACKEND }); + } catch (error) { + this.onUnexpectedError(error, request); } - // Store a snapshot of files to the snapshot file - await parcelWatcherLib.writeSnapshot(realPath, snapshotFile, { ignore: this.addPredefinedExcludes(request.excludes), backend: ParcelWatcher.PARCEL_WATCHER_BACKEND }); - // Signal we are ready now when the first snapshot was written if (counter === 1) { instance.complete(); @@ -578,6 +588,12 @@ export class ParcelWatcher extends BaseWatcher implements IRecursiveWatcherWithS } } + // Version 2.5.1 introduces 3 new errors on macOS + // via https://github.dev/parcel-bundler/watcher/pull/196 + else if (msg.indexOf('File system must be re-scanned') !== -1) { + this.error(msg, request); + } + // Any other error is unexpected and we should try to // restart the watcher as a result to get into healthy // state again if possible and if not attempted too much diff --git a/src/vs/platform/files/test/node/parcelWatcher.test.ts b/src/vs/platform/files/test/node/parcelWatcher.test.ts index e2038a17f50e..40dfc38b49cf 100644 --- a/src/vs/platform/files/test/node/parcelWatcher.test.ts +++ b/src/vs/platform/files/test/node/parcelWatcher.test.ts @@ -748,11 +748,11 @@ suite.skip('File Watcher (parcel)', function () { assert.strictEqual(instance.failed, true); }); - (isWindows /* Windows: times out for some reason */ ? test.skip : test)('watch requests support suspend/resume (folder, does not exist in beginning, not reusing watcher)', async () => { + (!isMacintosh /* Linux/Windows: times out for some reason */ ? test.skip : test)('watch requests support suspend/resume (folder, does not exist in beginning, not reusing watcher)', async () => { await testWatchFolderDoesNotExist(false); }); - (!isMacintosh /* Linux/Windows: times out for some reason */ ? test.skip : test)('watch requests support suspend/resume (folder, does not exist in beginning, reusing watcher)', async () => { + test('watch requests support suspend/resume (folder, does not exist in beginning, reusing watcher)', async () => { await testWatchFolderDoesNotExist(true); }); @@ -805,11 +805,11 @@ suite.skip('File Watcher (parcel)', function () { } } - (isWindows /* Windows: times out for some reason */ ? test.skip : test)('watch requests support suspend/resume (folder, exist in beginning, not reusing watcher)', async () => { + (!isMacintosh /* Linux/Windows: times out for some reason */ ? test.skip : test)('watch requests support suspend/resume (folder, exist in beginning, not reusing watcher)', async () => { await testWatchFolderExists(false); }); - (!isMacintosh /* Linux/Windows: times out for some reason */ ? test.skip : test)('watch requests support suspend/resume (folder, exist in beginning, reusing watcher)', async () => { + test('watch requests support suspend/resume (folder, exist in beginning, reusing watcher)', async () => { await testWatchFolderExists(true); }); From 8bc418429f00fed6824c6afda5ba038494526300 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 31 Jan 2025 11:56:15 +0100 Subject: [PATCH 0232/2632] Fix end of line trimming in inline completions (#239299) fix end of line trimming --- .../browser/model/inlineCompletionsSource.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts index 96cb605003a9..e7e579204e5f 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource.ts @@ -378,8 +378,9 @@ export class InlineCompletionWithUpdatedRange { } private _toIndividualEdits(editRange: Range, _replaceText: string): OffsetEdit { + const eol = this._textModel.getEOL(); const editOriginalText = this._textModel.getValueInRange(editRange); - const editReplaceText = _replaceText.replace(/\r\n|\r|\n/g, this._textModel.getEOL()); + const editReplaceText = _replaceText.replace(/\r\n|\r|\n/g, eol); const diffAlgorithm = linesDiffComputers.getDefault(); const lineDiffs = diffAlgorithm.computeDiff( @@ -414,7 +415,15 @@ export class InlineCompletionWithUpdatedRange { const startOffset = this._textModel.getOffsetAt(range.getStartPosition()); const endOffset = this._textModel.getOffsetAt(range.getEndPosition()); const originalRange = OffsetRange.ofStartAndLength(startOffset, endOffset - startOffset); - return new SingleOffsetEdit(originalRange, modifiedText.getValueOfRange(c.modifiedRange)); + + // TODO: EOL are not properly trimmed by the diffAlgorithm #12680 + const replaceText = modifiedText.getValueOfRange(c.modifiedRange); + const oldText = this._textModel.getValueInRange(range); + if (replaceText.endsWith(eol) && oldText.endsWith(eol)) { + return new SingleOffsetEdit(originalRange.deltaEnd(-eol.length), replaceText.slice(0, -eol.length)); + } + + return new SingleOffsetEdit(originalRange, replaceText); }) ); } From bdbd003442332bddb7ff5e1cb38b4184ae6ce6ec Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 31 Jan 2025 12:17:22 +0100 Subject: [PATCH 0233/2632] make sure to reveal/restore only once per chat request (#239306) https://github.com/microsoft/vscode/issues/239301 --- .../chat/browser/chatEditorController.ts | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorController.ts b/src/vs/workbench/contrib/chat/browser/chatEditorController.ts index a3f4439acb08..13e98f95ec93 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorController.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorController.ts @@ -6,7 +6,7 @@ import './media/chatEditorController.css'; import { addStandardDisposableListener, getTotalWidth } from '../../../../base/browser/dom.js'; import { Disposable, DisposableStore, dispose, toDisposable } from '../../../../base/common/lifecycle.js'; -import { autorun, autorunWithStore, derived, IObservable, observableFromEvent, observableValue } from '../../../../base/common/observable.js'; +import { autorun, autorunWithStore, derived, IObservable, observableFromEvent, observableFromEventOpts, observableValue } from '../../../../base/common/observable.js'; import { themeColorFromId } from '../../../../base/common/themables.js'; import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IOverlayWidgetPositionCoordinates, IViewZone, MouseTargetType } from '../../../../editor/browser/editorBrowser.js'; import { LineSource, renderLines, RenderOptions } from '../../../../editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; @@ -127,8 +127,24 @@ export class ChatEditorController extends Disposable implements IEditorContribut return undefined; }); + const lastRequest = derived(r => { + const entry = entryForEditor.read(r); + if (!entry) { + return undefined; + } + return observableFromEventOpts( + { equalsFn: (a, b) => a?.id === b?.id }, + entry.chatModel.onDidChange, () => entry.chatModel.getRequests().at(-1) + ).read(r); + }); let scrollState: StableEditorScrollState | undefined = undefined; + let didReveal = false; + this._register(autorun(r => { + const value = lastRequest.read(r); + scrollState = value ? StableEditorScrollState.capture(_editor) : undefined; + didReveal = false; + })); this._register(autorunWithStore((r, store) => { @@ -137,7 +153,6 @@ export class ChatEditorController extends Disposable implements IEditorContribut if (!currentEditorEntry) { this._ctxIsGlobalEditsSession.reset(); this._clear(); - scrollState = undefined; return; } @@ -146,13 +161,7 @@ export class ChatEditorController extends Disposable implements IEditorContribut return; } - const { session, chatModel, entries, idx, entry } = currentEditorEntry; - - const lastRequestSignal = observableFromEvent(this, chatModel.onDidChange, () => chatModel.getRequests().at(-1)); - store.add(autorun(r => { - lastRequestSignal.read(r); - scrollState ??= StableEditorScrollState.capture(this._editor); - })); + const { session, entries, idx, entry } = currentEditorEntry; this._ctxIsGlobalEditsSession.set(session.isGlobalEditingSession); this._ctxReviewModelEnabled.set(entry.reviewMode.read(r)); @@ -206,12 +215,13 @@ export class ChatEditorController extends Disposable implements IEditorContribut this._clearDiffRendering(); } - if (lastRequestSignal.read(r)?.response?.isComplete) { - if (!diff.identical) { - this._reveal(true, false, ScrollType.Immediate); - } else { + if (lastRequest.read(r)?.response?.isComplete) { + if (diff.identical) { scrollState?.restore(_editor); scrollState = undefined; + } else if (!didReveal) { + this._reveal(true, false, ScrollType.Immediate); + didReveal = true; } } } From 4fdaa8ad09611095370d1420bef3309bf69e9868 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 31 Jan 2025 12:40:46 +0100 Subject: [PATCH 0234/2632] Use model EOL for insertion view new line shifting/trimming (#239313) use model EOL for insertion view new line shifting/trimming --- .../browser/view/inlineEdits/insertionView.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/insertionView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/insertionView.ts index be0cc7f963bd..0f545a59f56a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/insertionView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/insertionView.ts @@ -30,10 +30,11 @@ export class InlineEditsInsertionView extends Disposable implements IInlineEdits if (!state) { return undefined; } const textModel = this._editor.getModel()!; + const eol = textModel.getEOL(); - if (state.startColumn === 1 && state.lineNumber > 1 && textModel.getLineLength(state.lineNumber) !== 0 && state.text.endsWith('\n') && !state.text.startsWith('\n')) { + if (state.startColumn === 1 && state.lineNumber > 1 && textModel.getLineLength(state.lineNumber) !== 0 && state.text.endsWith(eol) && !state.text.startsWith(eol)) { const endOfLineColumn = textModel.getLineLength(state.lineNumber - 1) + 1; - return { lineNumber: state.lineNumber - 1, column: endOfLineColumn, text: '\n' + state.text.slice(0, -1) }; + return { lineNumber: state.lineNumber - 1, column: endOfLineColumn, text: eol + state.text.slice(0, -eol.length) }; } return { lineNumber: state.lineNumber, column: state.startColumn, text: state.text }; @@ -88,12 +89,12 @@ export class InlineEditsInsertionView extends Disposable implements IInlineEdits } this._editorObs.versionId.read(reader); const textModel = this._editor.getModel()!; + const eol = textModel.getEOL(); - const cleanText = state.text.replace('\r\n', '\n'); - const textBeforeInsertion = cleanText.startsWith('\n') ? '' : textModel.getValueInRange(new Range(state.lineNumber, 1, state.lineNumber, state.column)); + const textBeforeInsertion = state.text.startsWith(eol) ? '' : textModel.getValueInRange(new Range(state.lineNumber, 1, state.lineNumber, state.column)); const textAfterInsertion = textModel.getValueInRange(new Range(state.lineNumber, state.column, state.lineNumber, textModel.getLineLength(state.lineNumber) + 1)); - const text = textBeforeInsertion + cleanText + textAfterInsertion; - const lines = text.split('\n'); + const text = textBeforeInsertion + state.text + textAfterInsertion; + const lines = text.split(eol); const renderOptions = RenderOptions.fromEditor(this._editor).withSetWidth(false); const lineWidths = lines.map(line => { @@ -121,15 +122,16 @@ export class InlineEditsInsertionView extends Disposable implements IInlineEdits // Adjust for leading/trailing newlines const lineHeight = this._editor.getOption(EditorOption.lineHeight); + const eol = this._editor.getModel()!.getEOL(); let topTrim = 0; let bottomTrim = 0; let i = 0; - for (; i < text.length && text[i] === '\n'; i++) { + for (; i < text.length && text.startsWith(eol, i); i += eol.length) { topTrim += lineHeight; } - for (let j = text.length - 1; j > i && text[j] === '\n'; j--) { + for (let j = text.length; j > i && text.endsWith(eol, j); j -= eol.length) { bottomTrim += lineHeight; } From d57350fa13782963c56c07ea645d6fd7cd3c25b2 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 31 Jan 2025 13:03:35 +0100 Subject: [PATCH 0235/2632] TST should handle `undefined` correctly (#239315) re https://github.com/microsoft/vscode/issues/239274 --- src/vs/base/common/ternarySearchTree.ts | 34 +++++++++++++------ .../test/common/ternarySearchtree.test.ts | 21 ++++++++++++ 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/vs/base/common/ternarySearchTree.ts b/src/vs/base/common/ternarySearchTree.ts index f31f58e703ce..624b7c933270 100644 --- a/src/vs/base/common/ternarySearchTree.ts +++ b/src/vs/base/common/ternarySearchTree.ts @@ -247,17 +247,31 @@ export class UriIterator implements IKeyIterator { throw new Error(); } } + +abstract class Undef { + + static readonly Val: unique symbol = Symbol('undefined_placeholder'); + + static wrap(value: V | undefined): V | typeof Undef.Val { + return value === undefined ? Undef.Val : value; + } + + static unwrap(value: V | typeof Undef.Val): V | undefined { + return value === Undef.Val ? undefined : value as V; + } +} + class TernarySearchTreeNode { height: number = 1; segment!: string; - value: V | undefined; + value: V | typeof Undef.Val | undefined; key: K | undefined; left: TernarySearchTreeNode | undefined; mid: TernarySearchTreeNode | undefined; right: TernarySearchTreeNode | undefined; isEmpty(): boolean { - return !this.left && !this.mid && !this.right && !this.value; + return !this.left && !this.mid && !this.right && this.value === undefined; } rotateLeft() { @@ -401,8 +415,8 @@ export class TernarySearchTree { } // set value - const oldElement = node.value; - node.value = element; + const oldElement = Undef.unwrap(node.value); + node.value = Undef.wrap(element); node.key = key; // balance @@ -462,7 +476,7 @@ export class TernarySearchTree { } get(key: K): V | undefined { - return this._getNode(key)?.value; + return Undef.unwrap(this._getNode(key)?.value); } private _getNode(key: K) { @@ -644,13 +658,13 @@ export class TernarySearchTree { } else if (iter.hasNext()) { // mid iter.next(); - candidate = node.value || candidate; + candidate = Undef.unwrap(node.value) || candidate; node = node.mid; } else { break; } } - return node && node.value || candidate; + return node && Undef.unwrap(node.value) || candidate; } findSuperstr(key: K): IterableIterator<[K, V]> | undefined { @@ -678,7 +692,7 @@ export class TernarySearchTree { // collect if (!node.mid) { if (allowValue) { - return node.value; + return Undef.unwrap(node.value); } else { return undefined; } @@ -718,8 +732,8 @@ export class TernarySearchTree { if (node.left) { this._dfsEntries(node.left, bucket); } - if (node.value) { - bucket.push([node.key!, node.value]); + if (node.value !== undefined) { + bucket.push([node.key!, Undef.unwrap(node.value)!]); } if (node.mid) { this._dfsEntries(node.mid, bucket); diff --git a/src/vs/base/test/common/ternarySearchtree.test.ts b/src/vs/base/test/common/ternarySearchtree.test.ts index 592ef0a597cc..df36727e2d13 100644 --- a/src/vs/base/test/common/ternarySearchtree.test.ts +++ b/src/vs/base/test/common/ternarySearchtree.test.ts @@ -241,6 +241,27 @@ suite('Ternary Search Tree', () => { ); }); + test('TernarySearchTree - set w/ undefined', function () { + + const trie = TernarySearchTree.forStrings(); + trie.set('foobar', undefined); + trie.set('foobaz', 2); + + assert.strictEqual(trie.get('foobar'), undefined); + assert.strictEqual(trie.get('foobaz'), 2); + assert.strictEqual(trie.get('NOT HERE'), undefined); + + assert.ok(trie.has('foobaz')); + assert.ok(trie.has('foobar')); + assert.ok(!trie.has('NOT HERE')); + + assertTstDfs(trie, ['foobar', undefined], ['foobaz', 2]); // should check for undefined value + + const oldValue = trie.set('foobar', 3); + assert.strictEqual(oldValue, undefined); + assert.strictEqual(trie.get('foobar'), 3); + }); + test('TernarySearchTree - findLongestMatch', function () { const trie = TernarySearchTree.forStrings(); From 8c645ee6e4db8563cd32457b0f776fd4947ded10 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 31 Jan 2025 14:15:48 +0100 Subject: [PATCH 0236/2632] First link in dialog is not first in tab order. (fix #239267) (#239318) --- src/vs/base/browser/ui/dialog/dialog.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index a26806c6e435..0c357a671e78 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -324,10 +324,6 @@ export class Dialog extends Disposable { // Focus next element (with wrapping) if (evt.equals(KeyCode.Tab) || evt.equals(KeyCode.RightArrow)) { - if (focusedIndex === -1) { - focusedIndex = 0; // default to focus first element if none have focus - } - const newFocusedIndex = (focusedIndex + 1) % focusableElements.length; focusableElements[newFocusedIndex].focus(); } From 022ab4de6a9ec79d7225db43f78b0f72b2b58b7f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 05:25:23 -0800 Subject: [PATCH 0237/2632] Improve terminal setting both descriptions Fixes #239304 --- .../electron-sandbox/externalTerminal.contribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts index 21967d3de0c5..79d0c7dbd8f0 100644 --- a/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts @@ -107,7 +107,7 @@ export class ExternalTerminalContribution implements IWorkbenchContribution { enumDescriptions: [ nls.localize('terminal.explorerKind.integrated', "Use VS Code's integrated terminal."), nls.localize('terminal.explorerKind.external', "Use the configured external terminal."), - nls.localize('terminal.explorerKind.both', "Use the other two together.") + nls.localize('terminal.explorerKind.both', "Show both integrated and external terminal actions.") ], description: nls.localize('explorer.openInTerminalKind', "When opening a file from the Explorer in a terminal, determines what kind of terminal will be launched"), default: 'integrated' @@ -122,7 +122,7 @@ export class ExternalTerminalContribution implements IWorkbenchContribution { enumDescriptions: [ nls.localize('terminal.sourceControlRepositoriesKind.integrated', "Use VS Code's integrated terminal."), nls.localize('terminal.sourceControlRepositoriesKind.external', "Use the configured external terminal."), - nls.localize('terminal.sourceControlRepositoriesKind.both', "Use the other two together.") + nls.localize('terminal.sourceControlRepositoriesKind.both', "Show both integrated and external terminal actions.") ], description: nls.localize('sourceControlRepositories.openInTerminalKind', "When opening a repository from the Source Control Repositories view in a terminal, determines what kind of terminal will be launched"), default: 'integrated' From 6b7338d9d8f9a43d35e729e7e704a4ca05df76d6 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 31 Jan 2025 11:15:59 -0300 Subject: [PATCH 0238/2632] Adds debug descriptions (#239323) --- .../browser/view/inlineEdits/utils.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts index 3c80494c8ba3..b4338882ea4a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils.ts @@ -9,7 +9,7 @@ import { numberComparator } from '../../../../../../base/common/arrays.js'; import { findFirstMin } from '../../../../../../base/common/arraysFind.js'; import { BugIndicatingError } from '../../../../../../base/common/errors.js'; import { DisposableStore, IDisposable, toDisposable } from '../../../../../../base/common/lifecycle.js'; -import { derived, derivedObservableWithCache, IObservable, IReader, observableValue, transaction } from '../../../../../../base/common/observable.js'; +import { derived, derivedObservableWithCache, derivedOpts, IObservable, IReader, observableValue, transaction } from '../../../../../../base/common/observable.js'; import { OS } from '../../../../../../base/common/platform.js'; import { getIndentationLength, splitLines } from '../../../../../../base/common/strings.js'; import { URI } from '../../../../../../base/common/uri.js'; @@ -392,6 +392,7 @@ export abstract class ObserverNode { if (className) { if (hasObservable(className)) { this._deriveds.push(derived(this, reader => { + /** @description set.class */ setClassName(this._element, getClassName(className, reader)); })); } else { @@ -404,7 +405,7 @@ export abstract class ObserverNode { for (const [cssKey, cssValue] of Object.entries(value)) { const key = camelCaseToHyphenCase(cssKey); if (isObservable(cssValue)) { - this._deriveds.push(derived(this, reader => { + this._deriveds.push(derivedOpts({ owner: this, debugName: () => `set.style.${key}` }, reader => { this._element.style.setProperty(key, convertCssValue(cssValue.read(reader))); })); } else { @@ -414,6 +415,7 @@ export abstract class ObserverNode { } else if (key === 'tabIndex') { if (isObservable(value)) { this._deriveds.push(derived(this, reader => { + /** @description set.tabIndex */ this._element.tabIndex = value.read(reader) as any; })); } else { @@ -423,7 +425,7 @@ export abstract class ObserverNode { (this._element as any)[key] = value; } else { if (isObservable(value)) { - this._deriveds.push(derived(this, reader => { + this._deriveds.push(derivedOpts({ owner: this, debugName: () => `set.${key}` }, reader => { setOrRemoveAttribute(this._element, key, value.read(reader)); })); } else { @@ -453,6 +455,7 @@ export abstract class ObserverNode { } const d = derived(this, reader => { + /** @description set.children */ this._element.replaceChildren(...getChildren(reader, children)); }); this._deriveds.push(d); @@ -470,6 +473,7 @@ export abstract class ObserverNode { keepUpdated(store: DisposableStore): ObserverNodeWithElement { derived(reader => { + /** update */ this.readEffect(reader); }).recomputeInitiallyAndOnChange(store); return this as unknown as ObserverNodeWithElement; @@ -604,7 +608,9 @@ type Falsy = T extends false | undefined | null ? T : never; export function mapOutFalsy(obs: IObservable): IObservable> | Falsy> { const nonUndefinedObs = derivedObservableWithCache(undefined, (reader, lastValue) => obs.read(reader) || lastValue); - return derived(reader => { + return derivedOpts({ + debugName: () => `${obs.debugName}.mapOutFalsy` + }, reader => { nonUndefinedObs.read(reader); const val = obs.read(reader); if (!val) { From 5890706a4814ac77cf213b978fcd03801fe517e7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 06:21:01 -0800 Subject: [PATCH 0239/2632] Revert: Editor GPU: Pass content width through to scroll bar This was causing the dom and gpu view lines to both update scroll width which caused an infinite loop as that always triggers an update in the current animation frame. Fixes #239321 --- src/vs/editor/browser/gpu/gpu.ts | 6 +----- .../browser/gpu/renderStrategy/baseRenderStrategy.ts | 4 ++-- .../gpu/renderStrategy/fullFileRenderStrategy.ts | 8 +++----- .../gpu/renderStrategy/viewportRenderStrategy.ts | 9 ++++----- src/vs/editor/browser/gpu/viewGpuContext.ts | 2 -- src/vs/editor/browser/view.ts | 1 - .../browser/viewParts/viewLinesGpu/viewLinesGpu.ts | 11 ++--------- 7 files changed, 12 insertions(+), 29 deletions(-) diff --git a/src/vs/editor/browser/gpu/gpu.ts b/src/vs/editor/browser/gpu/gpu.ts index 500b167e59a9..7284d275c769 100644 --- a/src/vs/editor/browser/gpu/gpu.ts +++ b/src/vs/editor/browser/gpu/gpu.ts @@ -36,10 +36,6 @@ export interface IGpuRenderStrategy extends IDisposable { * Resets the render strategy, clearing all data and setting up for a new frame. */ reset(): void; - update(viewportData: ViewportData, viewLineOptions: ViewLineOptions): IGpuRenderStrategyUpdateResult; + update(viewportData: ViewportData, viewLineOptions: ViewLineOptions): number; draw(pass: GPURenderPassEncoder, viewportData: ViewportData): void; } - -export interface IGpuRenderStrategyUpdateResult { - localContentWidth: number; -} diff --git a/src/vs/editor/browser/gpu/renderStrategy/baseRenderStrategy.ts b/src/vs/editor/browser/gpu/renderStrategy/baseRenderStrategy.ts index 59c7a3e3a535..b5d9fc895ccb 100644 --- a/src/vs/editor/browser/gpu/renderStrategy/baseRenderStrategy.ts +++ b/src/vs/editor/browser/gpu/renderStrategy/baseRenderStrategy.ts @@ -7,7 +7,7 @@ import { ViewEventHandler } from '../../../common/viewEventHandler.js'; import type { ViewportData } from '../../../common/viewLayout/viewLinesViewportData.js'; import type { ViewContext } from '../../../common/viewModel/viewContext.js'; import type { ViewLineOptions } from '../../viewParts/viewLines/viewLineOptions.js'; -import type { IGpuRenderStrategy, IGpuRenderStrategyUpdateResult } from '../gpu.js'; +import type { IGpuRenderStrategy } from '../gpu.js'; import { GlyphRasterizer } from '../raster/glyphRasterizer.js'; import type { ViewGpuContext } from '../viewGpuContext.js'; @@ -31,6 +31,6 @@ export abstract class BaseRenderStrategy extends ViewEventHandler implements IGp } abstract reset(): void; - abstract update(viewportData: ViewportData, viewLineOptions: ViewLineOptions): IGpuRenderStrategyUpdateResult; + abstract update(viewportData: ViewportData, viewLineOptions: ViewLineOptions): number; abstract draw(pass: GPURenderPassEncoder, viewportData: ViewportData): void; } diff --git a/src/vs/editor/browser/gpu/renderStrategy/fullFileRenderStrategy.ts b/src/vs/editor/browser/gpu/renderStrategy/fullFileRenderStrategy.ts index 71f6cc94228f..c41efda089b1 100644 --- a/src/vs/editor/browser/gpu/renderStrategy/fullFileRenderStrategy.ts +++ b/src/vs/editor/browser/gpu/renderStrategy/fullFileRenderStrategy.ts @@ -16,7 +16,7 @@ import type { ViewLineOptions } from '../../viewParts/viewLines/viewLineOptions. import type { ITextureAtlasPageGlyph } from '../atlas/atlas.js'; import { createContentSegmenter, type IContentSegmenter } from '../contentSegmenter.js'; import { fullFileRenderStrategyWgsl } from './fullFileRenderStrategy.wgsl.js'; -import { BindingId, type IGpuRenderStrategyUpdateResult } from '../gpu.js'; +import { BindingId } from '../gpu.js'; import { GPULifecycle } from '../gpuDisposable.js'; import { quadVertices } from '../gpuUtils.js'; import { GlyphRasterizer } from '../raster/glyphRasterizer.js'; @@ -232,7 +232,7 @@ export class FullFileRenderStrategy extends BaseRenderStrategy { this._finalRenderedLine = 0; } - update(viewportData: ViewportData, viewLineOptions: ViewLineOptions): IGpuRenderStrategyUpdateResult { + update(viewportData: ViewportData, viewLineOptions: ViewLineOptions): number { // IMPORTANT: This is a hot function. Variables are pre-allocated and shared within the // loop. This is done so we don't need to trust the JIT compiler to do this optimization to // avoid potential additional blocking time in garbage collector which is a common cause of @@ -501,9 +501,7 @@ export class FullFileRenderStrategy extends BaseRenderStrategy { this._visibleObjectCount = visibleObjectCount; - return { - localContentWidth: absoluteOffsetX - }; + return visibleObjectCount; } draw(pass: GPURenderPassEncoder, viewportData: ViewportData): void { diff --git a/src/vs/editor/browser/gpu/renderStrategy/viewportRenderStrategy.ts b/src/vs/editor/browser/gpu/renderStrategy/viewportRenderStrategy.ts index b71aaff05bc6..f35ccc85edcf 100644 --- a/src/vs/editor/browser/gpu/renderStrategy/viewportRenderStrategy.ts +++ b/src/vs/editor/browser/gpu/renderStrategy/viewportRenderStrategy.ts @@ -16,7 +16,7 @@ import type { ViewContext } from '../../../common/viewModel/viewContext.js'; import type { ViewLineOptions } from '../../viewParts/viewLines/viewLineOptions.js'; import type { ITextureAtlasPageGlyph } from '../atlas/atlas.js'; import { createContentSegmenter, type IContentSegmenter } from '../contentSegmenter.js'; -import { BindingId, type IGpuRenderStrategyUpdateResult } from '../gpu.js'; +import { BindingId } from '../gpu.js'; import { GPULifecycle } from '../gpuDisposable.js'; import { quadVertices } from '../gpuUtils.js'; import { GlyphRasterizer } from '../raster/glyphRasterizer.js'; @@ -184,7 +184,7 @@ export class ViewportRenderStrategy extends BaseRenderStrategy { } } - update(viewportData: ViewportData, viewLineOptions: ViewLineOptions): IGpuRenderStrategyUpdateResult { + update(viewportData: ViewportData, viewLineOptions: ViewLineOptions): number { // IMPORTANT: This is a hot function. Variables are pre-allocated and shared within the // loop. This is done so we don't need to trust the JIT compiler to do this optimization to // avoid potential additional blocking time in garbage collector which is a common cause of @@ -394,9 +394,8 @@ export class ViewportRenderStrategy extends BaseRenderStrategy { this._activeDoubleBufferIndex = this._activeDoubleBufferIndex ? 0 : 1; this._visibleObjectCount = visibleObjectCount; - return { - localContentWidth: absoluteOffsetX - }; + + return visibleObjectCount; } draw(pass: GPURenderPassEncoder, viewportData: ViewportData): void { diff --git a/src/vs/editor/browser/gpu/viewGpuContext.ts b/src/vs/editor/browser/gpu/viewGpuContext.ts index 3cf8238c6c60..181c468f12c5 100644 --- a/src/vs/editor/browser/gpu/viewGpuContext.ts +++ b/src/vs/editor/browser/gpu/viewGpuContext.ts @@ -33,7 +33,6 @@ export class ViewGpuContext extends Disposable { readonly maxGpuCols = ViewportRenderStrategy.maxSupportedColumns; readonly canvas: FastDomNode; - readonly scrollWidthElement: FastDomNode; readonly ctx: GPUCanvasContext; static device: Promise; @@ -88,7 +87,6 @@ export class ViewGpuContext extends Disposable { this.canvas = createFastDomNode(document.createElement('canvas')); this.canvas.setClassName('editorCanvas'); - this.scrollWidthElement = createFastDomNode(document.createElement('div')); // Adjust the canvas size to avoid drawing under the scroll bar this._register(Event.runAndSubscribe(configurationService.onDidChangeConfiguration, e => { diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index e383a7c9b2cb..1e883bfcd2b2 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -256,7 +256,6 @@ export class View extends ViewEventHandler { this._overflowGuardContainer.appendChild(this._scrollbar.getDomNode()); if (this._viewGpuContext) { this._overflowGuardContainer.appendChild(this._viewGpuContext.canvas); - this._linesContent.appendChild(this._viewGpuContext.scrollWidthElement); } this._overflowGuardContainer.appendChild(scrollDecoration.getDomNode()); this._overflowGuardContainer.appendChild(this._overlayWidgets.getDomNode()); diff --git a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts index f7485a2f2b7b..a6cabd559512 100644 --- a/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts +++ b/src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts @@ -49,7 +49,6 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { private _initViewportData?: ViewportData[]; private _lastViewportData?: ViewportData; private _lastViewLineOptions?: ViewLineOptions; - private _maxLocalContentWidthSoFar = 0; private _device!: GPUDevice; private _renderPassDescriptor!: GPURenderPassDescriptor; @@ -430,6 +429,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { return true; } override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { return true; } override onFlushed(e: viewEvents.ViewFlushedEvent): boolean { return true; } + override onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { return true; } override onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { return true; } override onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { return true; } @@ -473,14 +473,7 @@ export class ViewLinesGpu extends ViewPart implements IViewLines { const options = new ViewLineOptions(this._context.configuration, this._context.theme.type); - const { localContentWidth } = this._renderStrategy.value!.update(viewportData, options); - - // Track the largest local content width so far in this session and use it as the scroll - // width. This is how the DOM renderer works as well, so you may not be able to scroll to - // the right in a file with long lines until you scroll down. - this._maxLocalContentWidthSoFar = Math.max(this._maxLocalContentWidthSoFar, localContentWidth / this._viewGpuContext.devicePixelRatio.get()); - this._context.viewModel.viewLayout.setMaxLineWidth(this._maxLocalContentWidthSoFar); - this._viewGpuContext.scrollWidthElement.setWidth(this._context.viewLayout.getScrollWidth()); + this._renderStrategy.value!.update(viewportData, options); this._updateAtlasStorageBufferAndTexture(); From 180efc38de0d5605c820ff70576f6a9fb9206fff Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 06:32:29 -0800 Subject: [PATCH 0240/2632] Tweak wording and share config between kind settings --- .../externalTerminal.contribution.ts | 42 ++++++++----------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts index 79d0c7dbd8f0..572445bbec5a 100644 --- a/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/electron-sandbox/externalTerminal.contribution.ts @@ -11,7 +11,7 @@ import { KeyMod, KeyCode } from '../../../../base/common/keyCodes.js'; import { IHistoryService } from '../../../services/history/common/history.js'; import { KeybindingsRegistry, KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { Schemas } from '../../../../base/common/network.js'; -import { IConfigurationRegistry, Extensions, ConfigurationScope } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { IConfigurationRegistry, Extensions, ConfigurationScope, type IConfigurationPropertySchema } from '../../../../platform/configuration/common/configurationRegistry.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../common/contributions.js'; import { IExternalTerminalService } from '../../../../platform/externalTerminal/electron-sandbox/externalTerminalService.js'; @@ -91,6 +91,20 @@ export class ExternalTerminalContribution implements IWorkbenchContribution { private async _updateConfiguration(): Promise { const terminals = await this._externalTerminalService.getDefaultTerminalForPlatforms(); const configurationRegistry = Registry.as(Extensions.Configuration); + const terminalKindProperties: Partial = { + type: 'string', + enum: [ + 'integrated', + 'external', + 'both' + ], + enumDescriptions: [ + nls.localize('terminal.kind.integrated', "Show the integrated terminal action."), + nls.localize('terminal.kind.external', "Show the external terminal action."), + nls.localize('terminal.kind.both', "Show both integrated and external terminal actions.") + ], + default: 'integrated' + }; configurationRegistry.registerConfiguration({ id: 'externalTerminal', order: 100, @@ -98,34 +112,12 @@ export class ExternalTerminalContribution implements IWorkbenchContribution { type: 'object', properties: { 'terminal.explorerKind': { - type: 'string', - enum: [ - 'integrated', - 'external', - 'both' - ], - enumDescriptions: [ - nls.localize('terminal.explorerKind.integrated', "Use VS Code's integrated terminal."), - nls.localize('terminal.explorerKind.external', "Use the configured external terminal."), - nls.localize('terminal.explorerKind.both', "Show both integrated and external terminal actions.") - ], + ...terminalKindProperties, description: nls.localize('explorer.openInTerminalKind', "When opening a file from the Explorer in a terminal, determines what kind of terminal will be launched"), - default: 'integrated' }, 'terminal.sourceControlRepositoriesKind': { - type: 'string', - enum: [ - 'integrated', - 'external', - 'both' - ], - enumDescriptions: [ - nls.localize('terminal.sourceControlRepositoriesKind.integrated', "Use VS Code's integrated terminal."), - nls.localize('terminal.sourceControlRepositoriesKind.external', "Use the configured external terminal."), - nls.localize('terminal.sourceControlRepositoriesKind.both', "Show both integrated and external terminal actions.") - ], + ...terminalKindProperties, description: nls.localize('sourceControlRepositories.openInTerminalKind', "When opening a repository from the Source Control Repositories view in a terminal, determines what kind of terminal will be launched"), - default: 'integrated' }, 'terminal.external.windowsExec': { type: 'string', From f6a4de812d9d2c4e5c1395c2ec1313ae9e45e6eb Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Fri, 31 Jan 2025 15:50:54 +0100 Subject: [PATCH 0241/2632] Center gutter indicator icon in NES view (#239331) fixes https://github.com/microsoft/vscode-copilot/issues/12694 --- .../browser/view/inlineEdits/gutterIndicatorView.ts | 3 +++ .../inlineCompletions/browser/view/inlineEdits/view.css | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts index 72d5bf09400a..b1da2472750a 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/gutterIndicatorView.ts @@ -282,6 +282,9 @@ export class InlineEditsGutterIndicator extends Disposable { } }), transition: 'rotate 0.2s ease-in-out', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', } }, [ this._tabAction.map(v => v === 'accept' ? renderIcon(Codicon.keyboardTab) : renderIcon(Codicon.arrowRight)) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css index ac15ee3dd2d1..5e98709fda84 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.css @@ -263,10 +263,6 @@ } } -.inline-edits-view-gutter-indicator .codicon { - margin-top: 1px; /* TODO: Move into gutter DOM initialization */ -} - @keyframes wiggle { 0% { transform: rotate(0) scale(1); From 5c5cb3dc1fcb130cfed29d1c76588cd243d3548a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 06:46:50 -0800 Subject: [PATCH 0242/2632] Turn local echo off by default and downgrade to preview This feature has been a pain point for many remote users for a long time. Something critical to the feature is that it must maintain consistency and heal "mistakes", but unfortunately this isn't true and it can lead to buffer corruption and therefore could lead the user to run a command they didn't mean to. Additionally we don't disable the feature after it fails (#119103) or respect the ECHO termios mode and can therefore echo password characters to the user (#130821). My plan is to keep it off as a preview for the foreseeable future as we don't have plans to prioritize this work any time soon. Part of #126209 --- .../typeAhead/common/terminalTypeAheadConfiguration.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/typeAhead/common/terminalTypeAheadConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/typeAhead/common/terminalTypeAheadConfiguration.ts index 4f32102b09cc..789e4ed39c3b 100644 --- a/src/vs/workbench/contrib/terminalContrib/typeAhead/common/terminalTypeAheadConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/typeAhead/common/terminalTypeAheadConfiguration.ts @@ -29,6 +29,7 @@ export const terminalTypeAheadConfiguration: IStringDictionary Date: Fri, 31 Jan 2025 16:12:15 +0100 Subject: [PATCH 0243/2632] Copilot Edits: voice recording icon jumps to the right when activated (fix microsoft/vscode-copilot#12679) (#239333) --- .../actions/voiceChatActions.ts | 89 ++++++------------- 1 file changed, 26 insertions(+), 63 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 f3fe9985e72c..c7eb41e1b303 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -19,7 +19,7 @@ import { Action2, IAction2Options, MenuId } from '../../../../../platform/action import { CommandsRegistry, ICommandService } from '../../../../../platform/commands/common/commands.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { Extensions, IConfigurationRegistry } from '../../../../../platform/configuration/common/configurationRegistry.js'; -import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ContextKeyExpr, ContextKeyExpression, IContextKeyService, RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js'; import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; @@ -533,17 +533,28 @@ export class QuickVoiceChatAction extends VoiceChatWithHoldModeAction { } } +const primaryVoiceActionMenu = (when: ContextKeyExpression | undefined) => { + return [ + { + id: MenuId.ChatInput, + when: ContextKeyExpr.and(ContextKeyExpr.or(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)), when), + group: 'navigation', + order: 3 + }, + { + id: MenuId.ChatExecute, + when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession).negate(), when), + group: 'navigation', + order: 2 + } + ]; +}; + export class StartVoiceChatAction extends Action2 { static readonly ID = 'workbench.action.chat.startVoiceChat'; constructor() { - const menuCondition = ContextKeyExpr.and( - HasSpeechProvider, - ScopedChatSynthesisInProgress.negate(), // hide when text to speech is in progress - AnyScopedVoiceChatInProgress?.negate(), // hide when voice chat is in progress - ); - super({ id: StartVoiceChatAction.ID, title: localize2('workbench.action.chat.startVoiceChat.label', "Start Voice Chat"), @@ -565,20 +576,11 @@ export class StartVoiceChatAction extends Action2 { AnyChatRequestInProgress?.negate(), // disable when any chat request is in progress SpeechToTextInProgress.negate() // disable when speech to text is in progress ), - menu: [ - { - id: MenuId.ChatInput, - when: ContextKeyExpr.and(ContextKeyExpr.or(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)), menuCondition), - group: 'navigation', - order: 3 - }, - { - id: MenuId.ChatExecute, - when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession).negate(), menuCondition), - group: 'navigation', - order: 2 - } - ] + menu: primaryVoiceActionMenu(ContextKeyExpr.and( + HasSpeechProvider, + ScopedChatSynthesisInProgress.negate(), // hide when text to speech is in progress + AnyScopedVoiceChatInProgress?.negate(), // hide when voice chat is in progress + )) }); } @@ -613,20 +615,7 @@ export class StopListeningAction extends Action2 { }, icon: spinningLoading, precondition: GlobalVoiceChatInProgress, // need global context here because of `f1: true` - menu: [ - { - id: MenuId.ChatInput, - when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), AnyScopedVoiceChatInProgress), - group: 'navigation', - order: 3 - }, - { - id: MenuId.ChatExecute, - when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), AnyScopedVoiceChatInProgress), - group: 'navigation', - order: 2 - } - ] + menu: primaryVoiceActionMenu(AnyScopedVoiceChatInProgress) }); } @@ -960,20 +949,7 @@ export class StopReadAloud extends Action2 { primary: KeyCode.Escape, when: ScopedChatSynthesisInProgress }, - menu: [ - { - id: MenuId.ChatInput, - when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ScopedChatSynthesisInProgress), - group: 'navigation', - order: 3 - }, - { - id: MenuId.ChatExecute, - when: ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), ScopedChatSynthesisInProgress), - group: 'navigation', - order: 2 - } - ] + menu: primaryVoiceActionMenu(ScopedChatSynthesisInProgress) }); } @@ -1307,20 +1283,7 @@ export class InstallSpeechProviderForVoiceChatAction extends BaseInstallSpeechPr title: localize2('workbench.action.chat.installProviderForVoiceChat.label', "Start Voice Chat"), icon: Codicon.mic, precondition: InstallingSpeechProvider.negate(), - menu: [ - { - id: MenuId.ChatInput, - when: ContextKeyExpr.and(HasSpeechProvider.negate(), ContextKeyExpr.or(ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession))), - group: 'navigation', - order: 3 - }, - { - id: MenuId.ChatExecute, - when: ContextKeyExpr.and(HasSpeechProvider.negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.Panel).negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession).negate()), - group: 'navigation', - order: 2 - } - ] + menu: primaryVoiceActionMenu(HasSpeechProvider.negate()) }); } From d04d2d3bb0f8c165fc70229ca7c97222016379f1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 31 Jan 2025 16:50:17 +0100 Subject: [PATCH 0244/2632] edits context key cleanup (#239337) * debt - remove `isApplyingChatEdits` context key, it is the same as "requestInProgress" https://github.com/microsoft/vscode/issues/238742 * rename to xyzServiceImpl * move edits context keys into widget, not inside service * fix init order bug --- .../browser/actions/chatExecuteActions.ts | 19 +- .../contrib/chat/browser/chat.contribution.ts | 2 +- ...ngService.ts => chatEditingServiceImpl.ts} | 202 +++++++----------- .../contrib/chat/browser/chatEditorActions.ts | 10 +- .../contrib/chat/browser/chatWidget.ts | 67 ++++-- .../contrib/chat/common/chatEditingService.ts | 7 +- 6 files changed, 146 insertions(+), 161 deletions(-) rename src/vs/workbench/contrib/chat/browser/chatEditing/{chatEditingService.ts => chatEditingServiceImpl.ts} (75%) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index cc5339b263cb..8bf302ae260e 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -16,7 +16,7 @@ import { KeybindingWeight } from '../../../../../platform/keybinding/common/keyb import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { applyingChatEditsContextKey, IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js'; +import { IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { chatAgentLeader, extractAgentAndCommand } from '../../common/chatParserTypes.js'; import { IChatService } from '../../common/chatService.js'; import { EditsViewId, IChatWidget, IChatWidgetService } from '../chat.js'; @@ -218,7 +218,7 @@ export class ChatEditingSessionSubmitAction extends SubmitAction { // if the input has prompt instructions attached, allow submitting requests even // without text present - having instructions is enough context for a request ContextKeyExpr.or(ChatContextKeys.inputHasText, ChatContextKeys.instructionsAttached), - applyingChatEditsContextKey.toNegated(), + ChatContextKeys.requestInProgress.negate(), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), ); @@ -238,13 +238,13 @@ export class ChatEditingSessionSubmitAction extends SubmitAction { { id: MenuId.ChatExecuteSecondary, group: 'group_1', - when: ContextKeyExpr.and(ContextKeyExpr.or(ChatContextKeys.isRequestPaused, ChatContextKeys.requestInProgress.negate()), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey.toNegated()), + when: ContextKeyExpr.and(ContextKeyExpr.or(ChatContextKeys.isRequestPaused, ChatContextKeys.requestInProgress.negate()), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)), order: 1 }, { id: MenuId.ChatExecute, order: 4, - when: ContextKeyExpr.and(ContextKeyExpr.or(ChatContextKeys.isRequestPaused, ChatContextKeys.requestInProgress.negate()), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey.toNegated()), + when: ContextKeyExpr.and(ContextKeyExpr.or(ChatContextKeys.isRequestPaused, ChatContextKeys.requestInProgress.negate()), ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession)), group: 'navigation', }, ] @@ -517,10 +517,7 @@ export class CancelAction extends Action2 { icon: Codicon.stopCircle, menu: { id: MenuId.ChatExecute, - when: ContextKeyExpr.or( - ContextKeyExpr.and(ChatContextKeys.isRequestPaused.negate(), ChatContextKeys.requestInProgress), - ContextKeyExpr.and(ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), applyingChatEditsContextKey) - ), + when: ContextKeyExpr.and(ChatContextKeys.isRequestPaused.negate(), ChatContextKeys.requestInProgress), order: 4, group: 'navigation', }, @@ -545,12 +542,6 @@ export class CancelAction extends Action2 { if (widget.viewModel) { chatService.cancelCurrentRequestForSession(widget.viewModel.sessionId); } - - const chatEditingService = accessor.get(IChatEditingService); - const currentEditingSession = chatEditingService.currentEditingSession; - if (currentEditingSession && currentEditingSession?.chatSessionId === widget.viewModel?.sessionId) { - chatEditingService.currentAutoApplyOperation?.cancel(); - } } } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 6b6889506b52..cf1d9b2ebf81 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -54,7 +54,7 @@ import { IChatAccessibilityService, IChatCodeBlockContextProviderService, IChatW import { ChatAccessibilityService } from './chatAccessibilityService.js'; import './chatAttachmentModel.js'; import { ChatMarkdownAnchorService, IChatMarkdownAnchorService } from './chatContentParts/chatMarkdownAnchorService.js'; -import { ChatEditingService } from './chatEditing/chatEditingService.js'; +import { ChatEditingService } from './chatEditing/chatEditingServiceImpl.js'; import { ChatEditor, IChatEditorOptions } from './chatEditor.js'; import { registerChatEditorActions } from './chatEditorActions.js'; import { ChatEditorController } from './chatEditorController.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts similarity index 75% rename from src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts rename to src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts index 1aba7d7c8cdb..3da10d9ae3c8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts @@ -5,7 +5,7 @@ import { coalesce, compareBy, delta } from '../../../../../base/common/arrays.js'; import { AsyncIterableSource } from '../../../../../base/common/async.js'; -import { CancellationToken, CancellationTokenSource } from '../../../../../base/common/cancellation.js'; +import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { BugIndicatingError, ErrorNoTelemetry } from '../../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; @@ -22,11 +22,10 @@ import { URI } from '../../../../../base/common/uri.js'; import { TextEdit } from '../../../../../editor/common/languages.js'; import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; import { localize } from '../../../../../nls.js'; -import { IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; +import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; -import { bindContextKey } from '../../../../../platform/observable/common/platformObservableUtils.js'; import { IProductService } from '../../../../../platform/product/common/productService.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js'; import { IWorkbenchAssignmentService } from '../../../../services/assignment/common/assignmentService.js'; @@ -37,8 +36,7 @@ import { ILifecycleService } from '../../../../services/lifecycle/common/lifecyc import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from '../../../multiDiffEditor/browser/multiDiffSourceResolverService.js'; import { CellUri } from '../../../notebook/common/notebookCommon.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; -import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { applyingChatEditsContextKey, applyingChatEditsFailedContextKey, CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingAgentSupportsReadonlyReferencesContextKey, chatEditingMaxFileAssignmentName, chatEditingResourceContextKey, ChatEditingSessionState, decidedChatEditingResourceContextKey, defaultChatEditingMaxFileLimit, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, IChatEditingSessionStream, IChatRelatedFile, IChatRelatedFilesProvider, IModifiedFileEntry, inChatEditingSessionContextKey, WorkingSetEntryState } from '../../common/chatEditingService.js'; +import { CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, chatEditingAgentSupportsReadonlyReferencesContextKey, chatEditingMaxFileAssignmentName, chatEditingResourceContextKey, ChatEditingSessionState, defaultChatEditingMaxFileLimit, IChatEditingService, IChatEditingSession, IChatEditingSessionStream, IChatRelatedFile, IChatRelatedFilesProvider, IModifiedFileEntry, inChatEditingSessionContextKey, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { IChatResponseModel, IChatTextEditGroup } from '../../common/chatModel.js'; import { IChatService } from '../../common/chatService.js'; import { ChatEditingModifiedFileEntry } from './chatEditingModifiedFileEntry.js'; @@ -66,11 +64,6 @@ export class ChatEditingService extends Disposable implements IChatEditingServic return result; }); - private readonly _currentAutoApplyOperationObs = observableValue(this, null); - get currentAutoApplyOperation(): CancellationTokenSource | null { - return this._currentAutoApplyOperationObs.get(); - } - get currentEditingSession(): IChatEditingSession | null { return this._currentSessionObs.get(); } @@ -91,8 +84,6 @@ export class ChatEditingService extends Disposable implements IChatEditingServic private _restoringEditingSession: Promise | undefined; - private _applyingChatEditsFailedContextKey: IContextKey; - private _chatRelatedFilesProviders = new Map(); constructor( @@ -113,54 +104,14 @@ export class ChatEditingService extends Disposable implements IChatEditingServic @IProductService productService: IProductService, ) { super(); - this._applyingChatEditsFailedContextKey = applyingChatEditsFailedContextKey.bindTo(contextKeyService); - this._applyingChatEditsFailedContextKey.set(false); this._register(decorationsService.registerDecorationsProvider(_instantiationService.createInstance(ChatDecorationsProvider, this._currentSessionObs))); this._register(multiDiffSourceResolverService.registerResolver(_instantiationService.createInstance(ChatEditingMultiDiffSourceResolver, this._currentSessionObs))); this._register(textModelService.registerTextModelContentProvider(ChatEditingTextModelContentProvider.scheme, _instantiationService.createInstance(ChatEditingTextModelContentProvider, this._currentSessionObs))); this._register(textModelService.registerTextModelContentProvider(ChatEditingSnapshotTextModelContentProvider.scheme, _instantiationService.createInstance(ChatEditingSnapshotTextModelContentProvider, this._currentSessionObs))); - this._register(bindContextKey(decidedChatEditingResourceContextKey, contextKeyService, (reader) => { - const currentSession = this._currentSessionObs.read(reader); - if (!currentSession) { - return; - } - const entries = currentSession.entries.read(reader); - const decidedEntries = entries.filter(entry => entry.state.read(reader) !== WorkingSetEntryState.Modified); - return decidedEntries.map(entry => entry.entryId); - })); - this._register(bindContextKey(hasUndecidedChatEditingResourceContextKey, contextKeyService, (reader) => { - for (const session of this.editingSessionsObs.read(reader)) { - const entries = session.entries.read(reader); - const decidedEntries = entries.filter(entry => entry.state.read(reader) === WorkingSetEntryState.Modified); - return decidedEntries.length > 0; - } - return false; - })); - this._register(bindContextKey(hasAppliedChatEditsContextKey, contextKeyService, (reader) => { - const currentSession = this._currentSessionObs.read(reader); - if (!currentSession) { - return false; - } - const entries = currentSession.entries.read(reader); - return entries.length > 0; - })); - this._register(bindContextKey(inChatEditingSessionContextKey, contextKeyService, (reader) => { - return this._currentSessionObs.read(reader) !== null; - })); - this._register(bindContextKey(applyingChatEditsContextKey, contextKeyService, (reader) => { - return this._currentAutoApplyOperationObs.read(reader) !== null; - })); - this._register(bindContextKey(ChatContextKeys.chatEditingCanUndo, contextKeyService, (r) => { - return this._currentSessionObs.read(r)?.canUndo.read(r) || false; - })); - this._register(bindContextKey(ChatContextKeys.chatEditingCanRedo, contextKeyService, (r) => { - return this._currentSessionObs.read(r)?.canRedo.read(r) || false; - })); this._register(this._chatService.onDidDisposeSession((e) => { if (e.reason === 'cleared' && this._currentSessionObs.get()?.chatSessionId === e.sessionId) { - this._applyingChatEditsFailedContextKey.set(false); void this._currentSessionObs.get()?.stop(); } })); @@ -306,7 +257,6 @@ export class ChatEditingService extends Disposable implements IChatEditingServic if (responseModel.result?.errorDetails && !responseModel.result.errorDetails.responseIsIncomplete) { // Roll back everything session.restoreSnapshot(responseModel.requestId); - this._applyingChatEditsFailedContextKey.set(true); } editsSource?.resolve(); @@ -317,94 +267,98 @@ export class ChatEditingService extends Disposable implements IChatEditingServic const handleResponseParts = async (responseModel: IChatResponseModel) => { + + if (responseModel.isCanceled) { + return; + } + for (const part of responseModel.response.value) { - if (part.kind === 'codeblockUri' || part.kind === 'textEditGroup') { - // ensure editor is open asap - if (!editedFilesExist.get(part.uri)) { - const uri = part.uri.scheme === Schemas.vscodeNotebookCell ? CellUri.parse(part.uri)?.notebook ?? part.uri : part.uri; - editedFilesExist.set(part.uri, this._fileService.exists(uri).then((e) => { - if (e) { - this._editorService.openEditor({ resource: uri, options: { inactive: true, preserveFocus: true, pinned: true } }); - } - return e; - })); - } + if (part.kind !== 'codeblockUri' && part.kind !== 'textEditGroup') { + continue; + } + // ensure editor is open asap + if (!editedFilesExist.get(part.uri)) { + const uri = part.uri.scheme === Schemas.vscodeNotebookCell ? CellUri.parse(part.uri)?.notebook ?? part.uri : part.uri; + editedFilesExist.set(part.uri, this._fileService.exists(uri).then((e) => { + if (e) { + this._editorService.openEditor({ resource: uri, options: { inactive: true, preserveFocus: true, pinned: true } }); + } + return e; + })); + } - // get new edits and start editing session - const first = editsSeen.size === 0; - let entry = editsSeen.get(part.uri); - if (!entry) { - entry = { seen: 0 }; - editsSeen.set(part.uri, entry); - } + // get new edits and start editing session + const first = editsSeen.size === 0; + let entry = editsSeen.get(part.uri); + if (!entry) { + entry = { seen: 0 }; + editsSeen.set(part.uri, entry); + } - const allEdits: TextEdit[][] = part.kind === 'textEditGroup' ? part.edits : []; - const newEdits = allEdits.slice(entry.seen); - entry.seen += newEdits.length; + const allEdits: TextEdit[][] = part.kind === 'textEditGroup' ? part.edits : []; + const newEdits = allEdits.slice(entry.seen); + entry.seen += newEdits.length; - if (newEdits.length > 0 || entry.seen === 0) { - // only allow empty edits when having just started, ignore otherwise to avoid unneccessary work - editsSource ??= new AsyncIterableSource(); - editsSource.emitOne({ uri: part.uri, edits: newEdits, kind: 'textEditGroup', done: part.kind === 'textEditGroup' && part.done }); - } + if (newEdits.length > 0 || entry.seen === 0) { + // only allow empty edits when having just started, ignore otherwise to avoid unneccessary work + editsSource ??= new AsyncIterableSource(); + editsSource.emitOne({ uri: part.uri, edits: newEdits, kind: 'textEditGroup', done: part.kind === 'textEditGroup' && part.done }); + } + + if (first) { - if (first) { - - await editsPromise; - - editsPromise = this._continueEditingSession(session, async (builder, token) => { - for await (const item of editsSource!.asyncIterable) { - if (responseModel.isCanceled) { - break; - } - if (token.isCancellationRequested) { - break; - } - if (item.edits.length === 0) { - // EMPTY edit, just signal via empty edits that work is starting - builder.textEdits(item.uri, [], item.done ?? false, responseModel); - continue; - } - for (let i = 0; i < item.edits.length; i++) { - const group = item.edits[i]; - const isLastGroup = i === item.edits.length - 1; - builder.textEdits(item.uri, group, isLastGroup && (item.done ?? false), responseModel); - } + await editsPromise; + + editsPromise = this._continueEditingSession(session, async (builder) => { + for await (const item of editsSource!.asyncIterable) { + if (responseModel.isCanceled) { + break; } - }).finally(() => { - editsPromise = undefined; - }); - } + if (item.edits.length === 0) { + // EMPTY edit, just signal via empty edits that work is starting + builder.textEdits(item.uri, [], item.done ?? false, responseModel); + continue; + } + for (let i = 0; i < item.edits.length; i++) { + const group = item.edits[i]; + const isLastGroup = i === item.edits.length - 1; + builder.textEdits(item.uri, group, isLastGroup && (item.done ?? false), responseModel); + } + } + }).finally(() => { + editsPromise = undefined; + }); } } }; observerDisposables.add(chatModel.onDidChange(async e => { - if (e.kind === 'addRequest') { - session.createSnapshot(e.request.id); - this._applyingChatEditsFailedContextKey.set(false); - const responseModel = e.request.response; - if (responseModel) { + if (e.kind !== 'addRequest') { + return; + } + session.createSnapshot(e.request.id); + const responseModel = e.request.response; + if (!responseModel) { + return; + } + if (responseModel.isComplete) { + await handleResponseParts(responseModel); + onResponseComplete(responseModel); + } else { + const disposable = observerDisposables.add(responseModel.onDidChange(async () => { + await handleResponseParts(responseModel); if (responseModel.isComplete) { - await handleResponseParts(responseModel); onResponseComplete(responseModel); - } else { - const disposable = responseModel.onDidChange(async () => { - await handleResponseParts(responseModel); - if (responseModel.isComplete) { - onResponseComplete(responseModel); - disposable.dispose(); - } - }); + observerDisposables.delete(disposable); } - } + })); } })); observerDisposables.add(chatModel.onDidDispose(() => observerDisposables.dispose())); return observerDisposables; } - private async _continueEditingSession(session: ChatEditingSession, builder: (stream: IChatEditingSessionStream, token: CancellationToken) => Promise): Promise { + private async _continueEditingSession(session: ChatEditingSession, builder: (stream: IChatEditingSessionStream) => Promise): Promise { if (session.state.get() === ChatEditingSessionState.StreamingEdits) { throw new BugIndicatingError('Cannot continue session that is still streaming'); } @@ -415,13 +369,9 @@ export class ChatEditingService extends Disposable implements IChatEditingServic } }; session.acceptStreamingEditsStart(); - const cancellationTokenSource = new CancellationTokenSource(); - this._currentAutoApplyOperationObs.set(cancellationTokenSource, undefined); try { - await builder(stream, cancellationTokenSource.token); + await builder(stream); } finally { - cancellationTokenSource.dispose(); - this._currentAutoApplyOperationObs.set(null, undefined); session.resolve(); } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditorActions.ts index 94e831bd1adf..681e61fb730c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditorActions.ts @@ -14,7 +14,7 @@ import { ChatEditorController, ctxHasEditorModification, ctxReviewModeEnabled } import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; import { ACTIVE_GROUP, IEditorService } from '../../../services/editor/common/editorService.js'; -import { hasUndecidedChatEditingResourceContextKey, IChatEditingService } from '../common/chatEditingService.js'; +import { IChatEditingService } from '../common/chatEditingService.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; import { isEqual } from '../../../../base/common/resources.js'; import { Range } from '../../../../editor/common/core/range.js'; @@ -134,7 +134,7 @@ abstract class AcceptDiscardAction extends Action2 { ? localize2('accept2', 'Accept') : localize2('discard2', 'Discard'), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and(ctxHasEditorModification, hasUndecidedChatEditingResourceContextKey), + precondition: ContextKeyExpr.and(ctxHasEditorModification), icon: accept ? Codicon.check : Codicon.discard, @@ -212,7 +212,7 @@ class RejectHunkAction extends EditorAction2 { id: 'chatEditor.action.undoHunk', title: localize2('undo', 'Discard this Change'), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and(ctxHasEditorModification, ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), + precondition: ContextKeyExpr.and(ctxHasEditorModification, ChatContextKeys.requestInProgress.negate()), icon: Codicon.discard, f1: true, keybinding: { @@ -238,7 +238,7 @@ class AcceptHunkAction extends EditorAction2 { id: 'chatEditor.action.acceptHunk', title: localize2('acceptHunk', 'Accept this Change'), category: CHAT_CATEGORY, - precondition: ContextKeyExpr.and(ctxHasEditorModification, ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), + precondition: ContextKeyExpr.and(ctxHasEditorModification, ChatContextKeys.requestInProgress.negate()), icon: Codicon.check, f1: true, keybinding: { @@ -268,7 +268,7 @@ class OpenDiffAction extends EditorAction2 { condition: EditorContextKeys.inDiffEditor, icon: Codicon.goToFile, }, - precondition: ContextKeyExpr.and(ctxHasEditorModification, ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), + precondition: ContextKeyExpr.and(ctxHasEditorModification, ChatContextKeys.requestInProgress.negate()), icon: Codicon.diffSingle, keybinding: { when: EditorContextKeys.focus, diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 8858806e069b..d614ffcf1be8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -15,7 +15,7 @@ import { MarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore, IDisposable, MutableDisposable, combinedDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceSet } from '../../../../base/common/map.js'; import { Schemas } from '../../../../base/common/network.js'; -import { autorunWithStore, observableFromEvent } from '../../../../base/common/observable.js'; +import { autorunWithStore, observableFromEvent, observableValue } from '../../../../base/common/observable.js'; import { extUri, isEqual } from '../../../../base/common/resources.js'; import { isDefined } from '../../../../base/common/types.js'; import { URI } from '../../../../base/common/uri.js'; @@ -30,6 +30,7 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js'; import { WorkbenchObjectTree } from '../../../../platform/list/browser/listService.js'; import { ILogService } from '../../../../platform/log/common/log.js'; +import { bindContextKey } from '../../../../platform/observable/common/platformObservableUtils.js'; import { IProductService } from '../../../../platform/product/common/productService.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; @@ -38,7 +39,7 @@ import { asCssVariable } from '../../../../platform/theme/common/colorUtils.js'; import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { ChatAgentLocation, IChatAgentCommand, IChatAgentData, IChatAgentService, IChatWelcomeMessageContent, isChatWelcomeMessageContent } from '../common/chatAgents.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; -import { IChatEditingService, IChatEditingSession, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../common/chatEditingService.js'; +import { applyingChatEditsFailedContextKey, decidedChatEditingResourceContextKey, hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, inChatEditingSessionContextKey, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../common/chatEditingService.js'; import { ChatPauseState, IChatModel, IChatRequestVariableEntry, IChatResponseModel } from '../common/chatModel.js'; import { ChatRequestAgentPart, IParsedChatRequest, chatAgentLeader, chatSubcommandLeader, formatChatQuestion } from '../common/chatParserTypes.js'; import { ChatRequestParser } from '../common/chatRequestParser.js'; @@ -190,7 +191,7 @@ export class ChatWidget extends Disposable implements IChatWidget { return this._viewModel; } - private _editingSession: IChatEditingSession | undefined; + private readonly _editingSession = observableValue(this, undefined); private parsedChatRequest: IParsedChatRequest | undefined; get parsedInput() { @@ -241,6 +242,8 @@ export class ChatWidget extends Disposable implements IChatWidget { this.viewContext = _viewContext ?? {}; + const viewModelObs = observableFromEvent(this, this.onDidChangeViewModel, () => this.viewModel); + if (typeof location === 'object') { this._location = location; } else { @@ -255,10 +258,50 @@ export class ChatWidget extends Disposable implements IChatWidget { this.isRequestPaused = ChatContextKeys.isRequestPaused.bindTo(contextKeyService); this.canRequestBePaused = ChatContextKeys.canRequestBePaused.bindTo(contextKeyService); - this._codeBlockModelCollection = this._register(instantiationService.createInstance(CodeBlockModelCollection)); + this._register(bindContextKey(decidedChatEditingResourceContextKey, contextKeyService, (reader) => { + const currentSession = this._editingSession.read(reader); + if (!currentSession) { + return; + } + const entries = currentSession.entries.read(reader); + const decidedEntries = entries.filter(entry => entry.state.read(reader) !== WorkingSetEntryState.Modified); + return decidedEntries.map(entry => entry.entryId); + })); + this._register(bindContextKey(hasUndecidedChatEditingResourceContextKey, contextKeyService, (reader) => { + const currentSession = this._editingSession.read(reader); + const entries = currentSession?.entries.read(reader) ?? []; // using currentSession here + const decidedEntries = entries.filter(entry => entry.state.read(reader) === WorkingSetEntryState.Modified); + return decidedEntries.length > 0; + })); + this._register(bindContextKey(hasAppliedChatEditsContextKey, contextKeyService, (reader) => { + const currentSession = this._editingSession.read(reader); + if (!currentSession) { + return false; + } + const entries = currentSession.entries.read(reader); + return entries.length > 0; + })); + this._register(bindContextKey(inChatEditingSessionContextKey, contextKeyService, (reader) => { + return this._editingSession.read(reader) !== null; + })); + this._register(bindContextKey(ChatContextKeys.chatEditingCanUndo, contextKeyService, (r) => { + return this._editingSession.read(r)?.canUndo.read(r) || false; + })); + this._register(bindContextKey(ChatContextKeys.chatEditingCanRedo, contextKeyService, (r) => { + return this._editingSession.read(r)?.canRedo.read(r) || false; + })); + this._register(bindContextKey(applyingChatEditsFailedContextKey, contextKeyService, (r) => { + const chatModel = viewModelObs.read(r)?.model; + const editingSession = this._editingSession.read(r); + if (!editingSession || !chatModel) { + return false; + } + const lastResponse = observableFromEvent(this, chatModel.onDidChange, () => chatModel.getRequests().at(-1)?.response).read(r); + return lastResponse?.result?.errorDetails && !lastResponse?.result?.errorDetails.responseIsIncomplete; + })); + this._codeBlockModelCollection = this._register(instantiationService.createInstance(CodeBlockModelCollection)); - const viewModelObs = observableFromEvent(this, this.onDidChangeViewModel, () => this.viewModel); this._register(autorunWithStore((r, store) => { @@ -266,7 +309,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const sessions = chatEditingService.editingSessionsObs.read(r); const session = sessions.find(candidate => candidate.chatSessionId === viewModel?.sessionId); - this._editingSession = undefined; + this._editingSession.set(undefined, undefined); this.renderChatEditingSessionState(); // this is necessary to make sure we dispose previous buttons, etc. if (!session) { @@ -274,13 +317,13 @@ export class ChatWidget extends Disposable implements IChatWidget { return; } - this._editingSession = session; + this._editingSession.set(session, undefined); store.add(session.onDidChange(() => { this.renderChatEditingSessionState(); })); store.add(session.onDidDispose(() => { - this._editingSession = undefined; + this._editingSession.set(undefined, undefined); this.renderChatEditingSessionState(); })); store.add(this.onDidChangeParsedInput(() => { @@ -602,7 +645,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (!this.inputPart) { return; } - this.inputPart.renderChatEditingSessionState(this._editingSession ?? null, this); + this.inputPart.renderChatEditingSessionState(this._editingSession.get() ?? null, this); if (this.bodyDimension) { this.layout(this.bodyDimension.height, this.bodyDimension.width); @@ -1063,7 +1106,7 @@ export class ChatWidget extends Disposable implements IChatWidget { // This should never exceed the maximum file entries limit above. for (const { uri, isMarkedReadonly } of this.inputPart.chatEditWorkingSetFiles) { // Skip over any suggested files that haven't been confirmed yet in the working set - if (currentEditingSession?.workingSet.get(uri)?.state === WorkingSetEntryState.Suggested) { + if (currentEditingSession.get()?.workingSet.get(uri)?.state === WorkingSetEntryState.Suggested) { unconfirmedSuggestions.add(uri); } else { uniqueWorkingSetEntries.add(uri); @@ -1090,7 +1133,7 @@ export class ChatWidget extends Disposable implements IChatWidget { // Make sure that any files that we sent are part of the working set // but do not permanently add file variables from previous requests to the working set // since the user may subsequently edit the chat history - currentEditingSession?.addFileToWorkingSet(file); + currentEditingSession.get()?.addFileToWorkingSet(file); } // Collect file variables from previous requests before sending the request @@ -1121,7 +1164,7 @@ export class ChatWidget extends Disposable implements IChatWidget { actualSize: number; }; this.telemetryService.publicLog2('chatEditing/workingSetSize', { originalSize: this.inputPart.attemptedWorkingSetEntriesCount, actualSize: uniqueWorkingSetEntries.size }); - currentEditingSession?.remove(WorkingSetEntryRemovalReason.User, ...unconfirmedSuggestions); + currentEditingSession.get()?.remove(WorkingSetEntryRemovalReason.User, ...unconfirmedSuggestions); } this.chatService.cancelCurrentRequestForSession(this.viewModel.sessionId); diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index 23d97adc4113..21560cda2cb2 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Event } from '../../../../base/common/event.js'; import { IDisposable } from '../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../base/common/map.js'; @@ -27,7 +27,6 @@ export interface IChatEditingService { readonly currentEditingSessionObs: IObservable; readonly currentEditingSession: IChatEditingSession | null; - readonly currentAutoApplyOperation: CancellationTokenSource | null; readonly editingSessionFileLimit: number; @@ -101,6 +100,8 @@ export interface IChatEditingSession { */ stop(clearState?: boolean): Promise; + readonly canUndo: IObservable; + readonly canRedo: IObservable; undoInteraction(): Promise; redoInteraction(): Promise; } @@ -126,6 +127,7 @@ export const enum ChatEditingSessionChangeType { } export interface IModifiedFileEntry { + readonly entryId: string; readonly originalURI: URI; readonly originalModel: ITextModel; readonly modifiedURI: URI; @@ -164,7 +166,6 @@ export const chatEditingAgentSupportsReadonlyReferencesContextKey = new RawConte export const decidedChatEditingResourceContextKey = new RawContextKey('decidedChatEditingResource', []); export const chatEditingResourceContextKey = new RawContextKey('chatEditingResource', undefined); export const inChatEditingSessionContextKey = new RawContextKey('inChatEditingSession', undefined); -export const applyingChatEditsContextKey = new RawContextKey('isApplyingChatEdits', undefined); export const hasUndecidedChatEditingResourceContextKey = new RawContextKey('hasUndecidedChatEditingResource', false); export const hasAppliedChatEditsContextKey = new RawContextKey('hasAppliedChatEdits', false); export const applyingChatEditsFailedContextKey = new RawContextKey('applyingChatEditsFailed', false); From 3bcb9a5d9ddd8a55dbaa984ede7e42680dae87e3 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 31 Jan 2025 16:56:13 +0100 Subject: [PATCH 0245/2632] EditContext : Synchornize clipboard.ts and view.ts EditContext information (#239341) adding code to synchornize clipboard.ts and view.ts --- src/vs/editor/browser/view.ts | 20 ++++++++++++------- .../contrib/clipboard/browser/clipboard.ts | 7 ++++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index 1e883bfcd2b2..e1edfb83b10d 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -42,7 +42,7 @@ import { ViewCursors } from './viewParts/viewCursors/viewCursors.js'; import { ViewZones } from './viewParts/viewZones/viewZones.js'; import { WhitespaceOverlay } from './viewParts/whitespace/whitespace.js'; import { IEditorConfiguration } from '../common/config/editorConfiguration.js'; -import { EditorOption } from '../common/config/editorOptions.js'; +import { EditorOption, IComputedEditorOptions } from '../common/config/editorOptions.js'; import { Position } from '../common/core/position.js'; import { Range } from '../common/core/range.js'; import { Selection } from '../common/core/selection.js'; @@ -148,7 +148,7 @@ export class View extends ViewEventHandler { // Keyboard handler this._experimentalEditContextEnabled = this._context.configuration.options.get(EditorOption.experimentalEditContextEnabled); this._accessibilitySupport = this._context.configuration.options.get(EditorOption.accessibilitySupport); - this._editContext = this._instantiateEditContext(this._experimentalEditContextEnabled, this._accessibilitySupport); + this._editContext = this._instantiateEditContext(); this._viewParts.push(this._editContext); @@ -277,10 +277,9 @@ export class View extends ViewEventHandler { this._pointerHandler = this._register(new PointerHandler(this._context, this._viewController, this._createPointerHandlerHelper())); } - private _instantiateEditContext(experimentalEditContextEnabled: boolean, accessibilitySupport: AccessibilitySupport): AbstractEditContext { - const domNode = dom.getWindow(this._overflowGuardContainer.domNode); - const isEditContextSupported = EditContext.supported(domNode); - if (experimentalEditContextEnabled && isEditContextSupported && accessibilitySupport !== AccessibilitySupport.Enabled) { + private _instantiateEditContext(): AbstractEditContext { + const usingExperimentalEditContext = useExperimentalEditContext(dom.getWindow(this._overflowGuardContainer.domNode), this._context.configuration.options); + if (usingExperimentalEditContext) { return this._instantiationService.createInstance(NativeEditContext, this._ownerID, this._context, this._overflowGuardContainer, this._viewController, this._createTextAreaHandlerHelper()); } else { return this._instantiationService.createInstance(TextAreaEditContext, this._context, this._overflowGuardContainer, this._viewController, this._createTextAreaHandlerHelper()); @@ -298,7 +297,7 @@ export class View extends ViewEventHandler { const isEditContextFocused = this._editContext.isFocused(); const indexOfEditContext = this._viewParts.indexOf(this._editContext); this._editContext.dispose(); - this._editContext = this._instantiateEditContext(experimentalEditContextEnabled, accessibilitySupport); + this._editContext = this._instantiateEditContext(); if (isEditContextFocused) { this._editContext.focus(); } @@ -852,3 +851,10 @@ class EditorRenderingCoordinator { } } } + +export function useExperimentalEditContext(activeWindow: CodeWindow, options: IComputedEditorOptions): boolean { + const isEditContextSupported = EditContext.supported(activeWindow); + const experimentalEditContextEnabled = options.get(EditorOption.experimentalEditContextEnabled); + const accessibilitySupport = options.get(EditorOption.accessibilitySupport); + return experimentalEditContextEnabled && isEditContextSupported && accessibilitySupport !== AccessibilitySupport.Enabled; +} diff --git a/src/vs/editor/contrib/clipboard/browser/clipboard.ts b/src/vs/editor/contrib/clipboard/browser/clipboard.ts index 15fc67e770b4..17484a5b1e4a 100644 --- a/src/vs/editor/contrib/clipboard/browser/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/browser/clipboard.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as browser from '../../../../base/browser/browser.js'; -import { getActiveDocument } from '../../../../base/browser/dom.js'; +import { getActiveDocument, getWindow } from '../../../../base/browser/dom.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import * as platform from '../../../../base/common/platform.js'; import * as nls from '../../../../nls.js'; @@ -18,6 +18,7 @@ import { NativeEditContextRegistry } from '../../../browser/controller/editConte import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { Command, EditorAction, MultiCommand, registerEditorAction } from '../../../browser/editorExtensions.js'; import { ICodeEditorService } from '../../../browser/services/codeEditorService.js'; +import { useExperimentalEditContext } from '../../../browser/view.js'; import { EditorOption } from '../../../common/config/editorOptions.js'; import { Handler } from '../../../common/editorCommon.js'; import { EditorContextKeys } from '../../../common/editorContextKeys.js'; @@ -235,8 +236,8 @@ if (PasteAction) { if (focusedEditor && focusedEditor.hasModel() && focusedEditor.hasTextFocus()) { // execCommand(paste) does not work with edit context let result: boolean; - const experimentalEditContextEnabled = focusedEditor.getOption(EditorOption.experimentalEditContextEnabled); - if (experimentalEditContextEnabled) { + const usingExperimentalEditContext = useExperimentalEditContext(getWindow(focusedEditor.getDomNode()), focusedEditor.getOptions()); + if (usingExperimentalEditContext) { // Since we can not call execCommand('paste') on a dom node with edit context set // we added a hidden text area that receives the paste execution // see nativeEditContext.ts for more details From 9add868cda75ab3d669f24c15d2f3b5b81a13afe Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 08:30:51 -0800 Subject: [PATCH 0246/2632] Add very basic tokenizer for command line Fixes #239018 --- .../src/terminalSuggestMain.ts | 35 ++++--------- .../terminal-suggest/src/test/tokens.test.ts | 52 +++++++++++++++++++ extensions/terminal-suggest/src/tokens.ts | 33 ++++++++++++ 3 files changed, 94 insertions(+), 26 deletions(-) create mode 100644 extensions/terminal-suggest/src/test/tokens.test.ts create mode 100644 extensions/terminal-suggest/src/tokens.ts diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 3cd5854ca74c..db52610be6c9 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -12,6 +12,7 @@ import cdSpec from './completions/cd'; import codeInsidersCompletionSpec from './completions/code-insiders'; import { osIsWindows } from './helpers/os'; import { isExecutable } from './helpers/executable'; +import { getTokenType, TokenType } from './tokens'; const enum PwshCommandType { Alias = 1 @@ -136,7 +137,8 @@ export async function activate(context: vscode.ExtensionContext) { const prefix = getPrefix(terminalContext.commandLine, terminalContext.cursorPosition); const pathSeparator = isWindows ? '\\' : '/'; - const result = await getCompletionItemsFromSpecs(availableSpecs, terminalContext, commands, prefix, terminal.shellIntegration?.cwd, token); + const tokenType = getTokenType(terminalContext, shellType); + const result = await getCompletionItemsFromSpecs(availableSpecs, terminalContext, commands, prefix, tokenType, terminal.shellIntegration?.cwd, token); if (terminal.shellIntegration?.env) { const homeDirCompletion = result.items.find(i => i.label === '~'); if (homeDirCompletion && terminal.shellIntegration.env.HOME) { @@ -324,6 +326,7 @@ export async function getCompletionItemsFromSpecs( terminalContext: { commandLine: string; cursorPosition: number }, availableCommands: ICompletionResource[], prefix: string, + tokenType: TokenType, shellIntegrationCwd?: vscode.Uri, token?: vscode.CancellationToken ): Promise<{ items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; cwd?: vscode.Uri }> { @@ -331,7 +334,6 @@ export async function getCompletionItemsFromSpecs( let filesRequested = false; let foldersRequested = false; - const firstCommand = getFirstCommand(terminalContext.commandLine); const precedingText = terminalContext.commandLine.slice(0, terminalContext.cursorPosition + 1); for (const spec of specs) { @@ -347,14 +349,10 @@ export async function getCompletionItemsFromSpecs( continue; } - if ( - // If the prompt is empty - !terminalContext.commandLine - // or the first command matches the command - || !!firstCommand && specLabel.startsWith(firstCommand) - ) { - // push it to the completion items + // push it to the completion items + if (tokenType === TokenType.Command) { items.push(createCompletionItem(terminalContext.cursorPosition, prefix, { label: specLabel }, getDescription(spec), availableCommand.detail)); + continue; } if (!terminalContext.commandLine.startsWith(specLabel)) { @@ -385,9 +383,7 @@ export async function getCompletionItemsFromSpecs( !filesRequested && !foldersRequested; - const shouldShowCommands = !terminalContext.commandLine.substring(0, terminalContext.cursorPosition).trimStart().includes(' '); - - if (shouldShowCommands && !filesRequested && !foldersRequested) { + if (tokenType === TokenType.Command) { // Include builitin/available commands in the results const labels = new Set(items.map((i) => i.label)); for (const command of availableCommands) { @@ -536,19 +532,6 @@ function getCompletionItemsFromArgs(args: Fig.SingleOrArray | undefined return { items, filesRequested, foldersRequested }; } - - -function getFirstCommand(commandLine: string): string | undefined { - const wordsOnLine = commandLine.split(' '); - let firstCommand: string | undefined = wordsOnLine[0]; - if (wordsOnLine.length > 1) { - firstCommand = undefined; - } else if (wordsOnLine.length === 0) { - firstCommand = commandLine; - } - return firstCommand; -} - function getFriendlyResourcePath(uri: vscode.Uri, pathSeparator: string, kind?: vscode.TerminalCompletionItemKind): string { let path = uri.fsPath; // Ensure drive is capitalized on Windows @@ -564,7 +547,7 @@ function getFriendlyResourcePath(uri: vscode.Uri, pathSeparator: string, kind?: } // TODO: remove once API is finalized -export enum TerminalShellType { +export const enum TerminalShellType { Sh = 1, Bash = 2, Fish = 3, diff --git a/extensions/terminal-suggest/src/test/tokens.test.ts b/extensions/terminal-suggest/src/test/tokens.test.ts new file mode 100644 index 000000000000..621a9b653883 --- /dev/null +++ b/extensions/terminal-suggest/src/test/tokens.test.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import { strictEqual } from 'node:assert'; +import { getTokenType, TokenType } from '../tokens'; +import { TerminalShellType } from '../terminalSuggestMain'; + +suite('Terminal Suggest', () => { + test('simple command', () => { + strictEqual(getTokenType({ commandLine: 'echo', cursorPosition: 'echo'.length }, undefined), TokenType.Command); + }); + test('simple argument', () => { + strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo hello'.length }, undefined), TokenType.Argument); + }); + test('simple command, cursor mid text', () => { + strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo'.length }, undefined), TokenType.Command); + }); + test('simple argument, cursor mid text', () => { + strictEqual(getTokenType({ commandLine: 'echo hello', cursorPosition: 'echo hel'.length }, undefined), TokenType.Argument); + }); + suite('reset to command', () => { + test('|', () => { + strictEqual(getTokenType({ commandLine: 'echo hello | ', cursorPosition: 'echo hello | '.length }, undefined), TokenType.Command); + }); + test(';', () => { + strictEqual(getTokenType({ commandLine: 'echo hello; ', cursorPosition: 'echo hello; '.length }, undefined), TokenType.Command); + }); + test('&&', () => { + strictEqual(getTokenType({ commandLine: 'echo hello && ', cursorPosition: 'echo hello && '.length }, undefined), TokenType.Command); + }); + test('||', () => { + strictEqual(getTokenType({ commandLine: 'echo hello || ', cursorPosition: 'echo hello || '.length }, undefined), TokenType.Command); + }); + }); + suite('pwsh', () => { + test('simple command', () => { + strictEqual(getTokenType({ commandLine: 'Write-Host', cursorPosition: 'Write-Host'.length }, TerminalShellType.PowerShell), TokenType.Command); + }); + test('simple argument', () => { + strictEqual(getTokenType({ commandLine: 'Write-Host hello', cursorPosition: 'Write-Host hello'.length }, TerminalShellType.PowerShell), TokenType.Argument); + }); + test('reset char', () => { + strictEqual(getTokenType({ commandLine: `Write-Host hello -and `, cursorPosition: `Write-Host hello -and `.length }, TerminalShellType.PowerShell), TokenType.Command); + }); + test('arguments after reset char', () => { + strictEqual(getTokenType({ commandLine: `Write-Host hello -and $true `, cursorPosition: `Write-Host hello -and $true `.length }, TerminalShellType.PowerShell), TokenType.Argument); + }); + }); +}); diff --git a/extensions/terminal-suggest/src/tokens.ts b/extensions/terminal-suggest/src/tokens.ts new file mode 100644 index 000000000000..9712d2b0bfee --- /dev/null +++ b/extensions/terminal-suggest/src/tokens.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TerminalShellType } from './terminalSuggestMain'; + +export const enum TokenType { + Command, + Argument, +} + +const shellTypeResetChars: { [key: number]: string[] | undefined } = { + [TerminalShellType.Bash]: ['>', '>>', '<', '2>', '2>>', '&>', '&>>', '|', '|&', '&&', '||', '&', ';', '(', '{', '<<'], + [TerminalShellType.Zsh]: ['>', '>>', '<', '2>', '2>>', '&>', '&>>', '<>', '|', '|&', '&&', '||', '&', ';', '(', '{', '<<', '<<<', '<('], + [TerminalShellType.PowerShell]: ['>', '>>', '<', '2>', '2>>', '*>', '*>>', '|', '-and', '-or', '-not', '!', '&', '-eq', '-ne', '-gt', '-lt', '-ge', '-le', '-like', '-notlike', '-match', '-notmatch', '-contains', '-notcontains', '-in', '-notin'] +}; + +const defaultShellTypeResetChars = shellTypeResetChars[TerminalShellType.Bash]!; + +export function getTokenType(ctx: { commandLine: string; cursorPosition: number }, shellType: TerminalShellType | undefined): TokenType { + const spaceIndex = ctx.commandLine.substring(0, ctx.cursorPosition).lastIndexOf(' '); + if (spaceIndex === -1) { + return TokenType.Command; + } + const tokenIndex = spaceIndex === -1 ? 0 : spaceIndex + 1; + const previousTokens = ctx.commandLine.substring(0, tokenIndex).trim(); + const commandResetChars = shellType === undefined ? defaultShellTypeResetChars : shellTypeResetChars[shellType] ?? defaultShellTypeResetChars; + if (commandResetChars.some(e => previousTokens.endsWith(e))) { + return TokenType.Command; + } + return TokenType.Argument; +} From 63c8c1557178f7b3d0fd923039cacf35e9d38e1f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 09:04:43 -0800 Subject: [PATCH 0247/2632] Pass token type into getCompletionItemsFromSpecs tests --- .../terminal-suggest/src/test/terminalSuggestMain.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index d72996b8f702..030e9d0db995 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -12,6 +12,7 @@ import codeCompletionSpec from '../completions/code'; import codeInsidersCompletionSpec from '../completions/code-insiders'; import type { Uri } from 'vscode'; import { basename } from 'path'; +import { getTokenType } from '../tokens'; const fixtureDir = vscode.Uri.joinPath(vscode.Uri.file(__dirname), '../../testWorkspace'); const testCwdParent = vscode.Uri.joinPath(fixtureDir, 'parent'); @@ -150,7 +151,8 @@ suite('Terminal Suggest', () => { const prefix = commandLine.slice(0, cursorPosition).split(' ').at(-1) || ''; const filesRequested = testSpec.expectedResourceRequests?.type === 'files' || testSpec.expectedResourceRequests?.type === 'both'; const foldersRequested = testSpec.expectedResourceRequests?.type === 'folders' || testSpec.expectedResourceRequests?.type === 'both'; - const result = await getCompletionItemsFromSpecs(completionSpecs, { commandLine, cursorPosition }, availableCommands.map(c => { return { label: c }; }), prefix, testCwd); + const terminalContext = { commandLine, cursorPosition }; + const result = await getCompletionItemsFromSpecs(completionSpecs, terminalContext, availableCommands.map(c => { return { label: c }; }), prefix, getTokenType(terminalContext, undefined), testCwd); deepStrictEqual(result.items.map(i => i.label).sort(), (testSpec.expectedCompletions ?? []).sort()); strictEqual(result.filesRequested, filesRequested); strictEqual(result.foldersRequested, foldersRequested); From 999cf4704bb66546de23b5b8165f09178083557e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 09:56:24 -0800 Subject: [PATCH 0248/2632] Pull in bash aliases, add alias kind Part of #239028 --- .../src/terminalSuggestMain.ts | 27 ++++++++++++++++--- .../common/xterm/shellIntegrationAddon.ts | 4 +++ src/vs/workbench/api/common/extHostTypes.ts | 3 ++- .../common/scripts/shellIntegration-bash.sh | 5 ++++ .../browser/terminalCompletionService.ts | 3 ++- .../suggest/browser/terminalSuggestAddon.ts | 3 ++- ...e.proposed.terminalCompletionProvider.d.ts | 3 ++- 7 files changed, 40 insertions(+), 8 deletions(-) diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 3cd5854ca74c..6ef58066d42e 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import * as fs from 'fs/promises'; import * as path from 'path'; -import { exec, ExecOptionsWithStringEncoding, execSync } from 'child_process'; +import { exec, ExecOptionsWithStringEncoding, execSync, spawnSync } from 'child_process'; import { upstreamSpecs } from './constants'; import codeCompletionSpec from './completions/code'; import cdSpec from './completions/cd'; @@ -45,11 +45,27 @@ async function getBuiltinCommands(shellType: TerminalShellType, existingCommands return; } const options: ExecOptionsWithStringEncoding = { encoding: 'utf-8', shell }; - let commands: string[] | undefined; + let commands: (string | ICompletionResource)[] | undefined; switch (shellType) { case TerminalShellType.Bash: { const bashOutput = execSync('compgen -b', options); commands = bashOutput.split('\n').filter(filter); + + // This must be run with interactive, otherwise there's a good chance aliases won't + // be set up + const bashOutput2 = spawnSync('bash', ['-ic', 'alias'], options).stdout; + for (const line of bashOutput2.split('\n')) { + const match = line.match(/^alias (?[a-zA-Z]+)='(?.+)'$/); + if (!match?.groups) { + continue; + } + commands.push({ + label: match.groups.alias, + detail: match.groups.resolved, + kind: vscode.TerminalCompletionItemKind.Alias, + }); + } + break; } case TerminalShellType.Zsh: { @@ -89,6 +105,7 @@ async function getBuiltinCommands(shellType: TerminalShellType, existingCommands return { label: e.Name, detail: e.DisplayName, + kind: vscode.TerminalCompletionItemKind.Alias, }; } default: { @@ -104,7 +121,7 @@ async function getBuiltinCommands(shellType: TerminalShellType, existingCommands } } - const commandResources = commands?.map(command => ({ label: command })); + const commandResources = commands?.map(command => typeof command === 'string' ? ({ label: command }) : command); cachedBuiltinCommands.set(shellType, commandResources); return commandResources; @@ -236,14 +253,16 @@ function createCompletionItem(cursorPosition: number, prefix: string, commandRes documentation, replacementIndex: cursorPosition - lastWord.length, replacementLength: lastWord.length, - kind: kind ?? vscode.TerminalCompletionItemKind.Method + kind: kind ?? commandResource.kind ?? vscode.TerminalCompletionItemKind.Method }; } interface ICompletionResource { label: string; detail?: string; + kind?: vscode.TerminalCompletionItemKind; } + async function getCommandsInPath(env: { [key: string]: string | undefined } = process.env): Promise<{ completionResources: Set | undefined; labels: Set | undefined } | undefined> { const labels: Set = new Set(); let pathValue: string | undefined; diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 49ed23c11037..9e23b749bfa2 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -508,6 +508,10 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati return true; } switch (key) { + case 'Aliases': { + console.log('aliases', value); + return true; + } case 'ContinuationPrompt': { this._updateContinuationPrompt(removeAnsiEscapeCodesFromPrompt(value)); return true; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 26ae6d8182ea..ad3bfb390e6c 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2140,7 +2140,8 @@ export enum TerminalCompletionItemKind { Folder = 1, Flag = 2, Method = 3, - Argument = 4 + Argument = 4, + Alias = 5 } export class TerminalCompletionItem implements vscode.TerminalCompletionItem { diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh index d36d7f401a36..1b0dc51e00d4 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh @@ -241,9 +241,14 @@ __vsc_continuation_end() { builtin printf '\e]633;G\a' } +__vsc_report_aliases() { + builtin printf '\e]633;P;Aliases=%s\a' "$(__vsc_escape_value "$(alias -p)")" $__vsc_nonce +} + __vsc_command_complete() { if [[ -z "${__vsc_first_prompt-}" ]]; then __vsc_update_cwd + __vsc_report_aliases builtin return fi if [ "$__vsc_current_command" = "" ]; then diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index 7bbc60a4baca..36d4de75fcd4 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -23,7 +23,8 @@ export enum TerminalCompletionItemKind { Folder = 1, Flag = 2, Method = 3, - Argument = 4 + Argument = 4, + Alias = 5, } export interface ITerminalCompletion extends ISimpleCompletion { diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index efc1efa72e5a..1af621de8cf1 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -84,7 +84,8 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest [TerminalCompletionItemKind.Folder, Codicon.folder], [TerminalCompletionItemKind.Flag, Codicon.symbolProperty], [TerminalCompletionItemKind.Method, Codicon.symbolMethod], - [TerminalCompletionItemKind.Argument, Codicon.symbolVariable] + [TerminalCompletionItemKind.Argument, Codicon.symbolVariable], + [TerminalCompletionItemKind.Alias, Codicon.replace], ]); private _shouldSyncWhenReady: boolean = false; diff --git a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts index 90e922f9304e..26a8a22e4843 100644 --- a/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.terminalCompletionProvider.d.ts @@ -61,7 +61,8 @@ declare module 'vscode' { Folder = 1, Flag = 2, Method = 3, - Argument = 4 + Argument = 4, + Alias = 5, } export interface TerminalCompletionContext { From da74a77c39fb755a4ca9b57f42f1e76c13a140c4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 10:09:30 -0800 Subject: [PATCH 0249/2632] Move bash logic into own file --- extensions/terminal-suggest/src/shell/bash.ts | 64 +++++++++++++++++++ .../src/terminalSuggestMain.ts | 28 ++------ extensions/terminal-suggest/src/types.ts | 12 ++++ 3 files changed, 80 insertions(+), 24 deletions(-) create mode 100644 extensions/terminal-suggest/src/shell/bash.ts create mode 100644 extensions/terminal-suggest/src/types.ts diff --git a/extensions/terminal-suggest/src/shell/bash.ts b/extensions/terminal-suggest/src/shell/bash.ts new file mode 100644 index 000000000000..0a6dfff74b3f --- /dev/null +++ b/extensions/terminal-suggest/src/shell/bash.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import type { ICompletionResource } from '../types'; +import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_process'; + +export async function getBashGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { + return [ + ...await getBashAliases(options), + ...await getBashBuiltins(options, existingCommands) + ]; +} + +async function getBashBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { + const compgenOutput = await new Promise((resolve, reject) => { + exec('compgen -b', options, (error, stdout) => { + if (error) { + reject(error); + } else { + resolve(stdout); + } + }); + }); + const filter = (cmd: string) => cmd && !existingCommands?.has(cmd); + return compgenOutput.split('\n').filter(filter); +} + +async function getBashAliases(options: ExecOptionsWithStringEncoding): Promise { + // This must be run with interactive, otherwise there's a good chance aliases won't + // be set up. Note that this could differ from the actual aliases as it's a new bash + // session, for the same reason this would not include aliases that are created + // by simply running `alias ...` in the terminal. + const aliasOutput = await new Promise((resolve, reject) => { + const child = spawn('bash', ['-ic', 'alias'], options); + let stdout = ''; + child.stdout.on('data', (data) => { + stdout += data; + }); + child.on('close', (code) => { + if (code !== 0) { + reject(new Error(`bash process exited with code ${code}`)); + } else { + resolve(stdout); + } + }); + }); + + const result: ICompletionResource[] = []; + for (const line of aliasOutput.split('\n')) { + const match = line.match(/^alias (?[a-zA-Z]+)='(?.+)'$/); + if (!match?.groups) { + continue; + } + result.push({ + label: match.groups.alias, + detail: match.groups.resolved, + kind: vscode.TerminalCompletionItemKind.Alias, + }); + } + return result; +} diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 6ef58066d42e..9f9aa5ab3b01 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -5,13 +5,15 @@ import * as vscode from 'vscode'; import * as fs from 'fs/promises'; import * as path from 'path'; -import { exec, ExecOptionsWithStringEncoding, execSync, spawnSync } from 'child_process'; +import { exec, ExecOptionsWithStringEncoding, execSync } from 'child_process'; import { upstreamSpecs } from './constants'; import codeCompletionSpec from './completions/code'; import cdSpec from './completions/cd'; import codeInsidersCompletionSpec from './completions/code-insiders'; import { osIsWindows } from './helpers/os'; import { isExecutable } from './helpers/executable'; +import type { ICompletionResource } from './types'; +import { getBashGlobals } from './shell/bash'; const enum PwshCommandType { Alias = 1 @@ -48,24 +50,7 @@ async function getBuiltinCommands(shellType: TerminalShellType, existingCommands let commands: (string | ICompletionResource)[] | undefined; switch (shellType) { case TerminalShellType.Bash: { - const bashOutput = execSync('compgen -b', options); - commands = bashOutput.split('\n').filter(filter); - - // This must be run with interactive, otherwise there's a good chance aliases won't - // be set up - const bashOutput2 = spawnSync('bash', ['-ic', 'alias'], options).stdout; - for (const line of bashOutput2.split('\n')) { - const match = line.match(/^alias (?[a-zA-Z]+)='(?.+)'$/); - if (!match?.groups) { - continue; - } - commands.push({ - label: match.groups.alias, - detail: match.groups.resolved, - kind: vscode.TerminalCompletionItemKind.Alias, - }); - } - + commands = await getBashGlobals(options); break; } case TerminalShellType.Zsh: { @@ -257,11 +242,6 @@ function createCompletionItem(cursorPosition: number, prefix: string, commandRes }; } -interface ICompletionResource { - label: string; - detail?: string; - kind?: vscode.TerminalCompletionItemKind; -} async function getCommandsInPath(env: { [key: string]: string | undefined } = process.env): Promise<{ completionResources: Set | undefined; labels: Set | undefined } | undefined> { const labels: Set = new Set(); diff --git a/extensions/terminal-suggest/src/types.ts b/extensions/terminal-suggest/src/types.ts new file mode 100644 index 000000000000..43b86a08a93e --- /dev/null +++ b/extensions/terminal-suggest/src/types.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +export interface ICompletionResource { + label: string; + detail?: string; + kind?: vscode.TerminalCompletionItemKind; +} From 61473cc52e08574ed5729aff82c8c5275dad74aa Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 10:10:24 -0800 Subject: [PATCH 0250/2632] Remove unused experiment --- .../platform/terminal/common/xterm/shellIntegrationAddon.ts | 4 ---- .../contrib/terminal/common/scripts/shellIntegration-bash.sh | 4 ---- 2 files changed, 8 deletions(-) diff --git a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts index 9e23b749bfa2..49ed23c11037 100644 --- a/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts +++ b/src/vs/platform/terminal/common/xterm/shellIntegrationAddon.ts @@ -508,10 +508,6 @@ export class ShellIntegrationAddon extends Disposable implements IShellIntegrati return true; } switch (key) { - case 'Aliases': { - console.log('aliases', value); - return true; - } case 'ContinuationPrompt': { this._updateContinuationPrompt(removeAnsiEscapeCodesFromPrompt(value)); return true; diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh index 1b0dc51e00d4..29457d232234 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh @@ -241,10 +241,6 @@ __vsc_continuation_end() { builtin printf '\e]633;G\a' } -__vsc_report_aliases() { - builtin printf '\e]633;P;Aliases=%s\a' "$(__vsc_escape_value "$(alias -p)")" $__vsc_nonce -} - __vsc_command_complete() { if [[ -z "${__vsc_first_prompt-}" ]]; then __vsc_update_cwd From 1908c617e840cf613b88bc86def8704a10aa244c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 10:24:50 -0800 Subject: [PATCH 0251/2632] zsh aliases --- extensions/terminal-suggest/src/shell/bash.ts | 2 +- extensions/terminal-suggest/src/shell/zsh.ts | 64 +++++++++++++++++++ .../src/terminalSuggestMain.ts | 6 +- .../common/scripts/shellIntegration-bash.sh | 1 - 4 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 extensions/terminal-suggest/src/shell/zsh.ts diff --git a/extensions/terminal-suggest/src/shell/bash.ts b/extensions/terminal-suggest/src/shell/bash.ts index 0a6dfff74b3f..93baf3d6e139 100644 --- a/extensions/terminal-suggest/src/shell/bash.ts +++ b/extensions/terminal-suggest/src/shell/bash.ts @@ -50,7 +50,7 @@ async function getBashAliases(options: ExecOptionsWithStringEncoding): Promise[a-zA-Z]+)='(?.+)'$/); + const match = line.match(/^alias (?[a-zA-Z0-9\.:-]+)='(?.+)'$/); if (!match?.groups) { continue; } diff --git a/extensions/terminal-suggest/src/shell/zsh.ts b/extensions/terminal-suggest/src/shell/zsh.ts new file mode 100644 index 000000000000..a19c71677ec1 --- /dev/null +++ b/extensions/terminal-suggest/src/shell/zsh.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import type { ICompletionResource } from '../types'; +import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_process'; + +export async function getZshGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { + return [ + ...await getZshBuiltins(options, existingCommands), + ...await getZshAliases(options), + ]; +} + +async function getZshBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { + const compgenOutput = await new Promise((resolve, reject) => { + exec('printf "%s\\n" ${(k)builtins}', options, (error, stdout) => { + if (error) { + reject(error); + } else { + resolve(stdout); + } + }); + }); + const filter = (cmd: string) => cmd && !existingCommands?.has(cmd); + return compgenOutput.split('\n').filter(filter); +} + +async function getZshAliases(options: ExecOptionsWithStringEncoding): Promise { + // This must be run with interactive, otherwise there's a good chance aliases won't + // be set up. Note that this could differ from the actual aliases as it's a new bash + // session, for the same reason this would not include aliases that are created + // by simply running `alias ...` in the terminal. + const aliasOutput = await new Promise((resolve, reject) => { + const child = spawn('zsh', ['-ic', 'alias'], options); + let stdout = ''; + child.stdout.on('data', (data) => { + stdout += data; + }); + child.on('close', (code) => { + if (code !== 0) { + reject(new Error(`zsh process exited with code ${code}`)); + } else { + resolve(stdout); + } + }); + }); + + const result: ICompletionResource[] = []; + for (const line of aliasOutput.split('\n')) { + const match = line.match(/^(?[a-zA-Z0-9\.:-]+)=(?:'(?.+)'|(?.+))$/); + if (!match?.groups) { + continue; + } + result.push({ + label: match.groups.alias, + detail: match.groups.resolved, + kind: vscode.TerminalCompletionItemKind.Alias, + }); + } + return result; +} diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 9f9aa5ab3b01..3b3b7286fd89 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -14,6 +14,7 @@ import { osIsWindows } from './helpers/os'; import { isExecutable } from './helpers/executable'; import type { ICompletionResource } from './types'; import { getBashGlobals } from './shell/bash'; +import { getZshGlobals } from './shell/zsh'; const enum PwshCommandType { Alias = 1 @@ -50,12 +51,11 @@ async function getBuiltinCommands(shellType: TerminalShellType, existingCommands let commands: (string | ICompletionResource)[] | undefined; switch (shellType) { case TerminalShellType.Bash: { - commands = await getBashGlobals(options); + commands = await getBashGlobals(options, existingCommands); break; } case TerminalShellType.Zsh: { - const zshOutput = execSync('printf "%s\\n" ${(k)builtins}', options); - commands = zshOutput.split('\n').filter(filter); + commands = await getZshGlobals(options, existingCommands); break; } case TerminalShellType.Fish: { diff --git a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh index 29457d232234..d36d7f401a36 100644 --- a/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh @@ -244,7 +244,6 @@ __vsc_continuation_end() { __vsc_command_complete() { if [[ -z "${__vsc_first_prompt-}" ]]; then __vsc_update_cwd - __vsc_report_aliases builtin return fi if [ "$__vsc_current_command" = "" ]; then From 02734d254f36dd1219b535c1c4917906bb3c5f89 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 10:29:06 -0800 Subject: [PATCH 0252/2632] Async fish and aliases --- extensions/terminal-suggest/src/shell/bash.ts | 8 +-- extensions/terminal-suggest/src/shell/fish.ts | 64 +++++++++++++++++++ extensions/terminal-suggest/src/shell/zsh.ts | 8 +-- .../src/terminalSuggestMain.ts | 6 +- 4 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 extensions/terminal-suggest/src/shell/fish.ts diff --git a/extensions/terminal-suggest/src/shell/bash.ts b/extensions/terminal-suggest/src/shell/bash.ts index 93baf3d6e139..70a8110eba3c 100644 --- a/extensions/terminal-suggest/src/shell/bash.ts +++ b/extensions/terminal-suggest/src/shell/bash.ts @@ -9,12 +9,12 @@ import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_proc export async function getBashGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { return [ - ...await getBashAliases(options), - ...await getBashBuiltins(options, existingCommands) + ...await getAliases(options), + ...await getBuiltins(options, existingCommands) ]; } -async function getBashBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { +async function getBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { const compgenOutput = await new Promise((resolve, reject) => { exec('compgen -b', options, (error, stdout) => { if (error) { @@ -28,7 +28,7 @@ async function getBashBuiltins(options: ExecOptionsWithStringEncoding, existingC return compgenOutput.split('\n').filter(filter); } -async function getBashAliases(options: ExecOptionsWithStringEncoding): Promise { +async function getAliases(options: ExecOptionsWithStringEncoding): Promise { // This must be run with interactive, otherwise there's a good chance aliases won't // be set up. Note that this could differ from the actual aliases as it's a new bash // session, for the same reason this would not include aliases that are created diff --git a/extensions/terminal-suggest/src/shell/fish.ts b/extensions/terminal-suggest/src/shell/fish.ts new file mode 100644 index 000000000000..f63ec5adf4db --- /dev/null +++ b/extensions/terminal-suggest/src/shell/fish.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import type { ICompletionResource } from '../types'; +import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_process'; + +export async function getFishGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { + return [ + ...await getAliases(options), + ...await getBuiltins(options, existingCommands), + ]; +} + +async function getBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { + const compgenOutput = await new Promise((resolve, reject) => { + exec('functions -n', options, (error, stdout) => { + if (error) { + reject(error); + } else { + resolve(stdout); + } + }); + }); + const filter = (cmd: string) => cmd && !existingCommands?.has(cmd); + return compgenOutput.split(', ').filter(filter); +} + +async function getAliases(options: ExecOptionsWithStringEncoding): Promise { + // This must be run with interactive, otherwise there's a good chance aliases won't + // be set up. Note that this could differ from the actual aliases as it's a new bash + // session, for the same reason this would not include aliases that are created + // by simply running `alias ...` in the terminal. + const aliasOutput = await new Promise((resolve, reject) => { + const child = spawn('fish', ['-ic', 'alias'], options); + let stdout = ''; + child.stdout.on('data', (data) => { + stdout += data; + }); + child.on('close', (code) => { + if (code !== 0) { + reject(new Error(`bash process exited with code ${code}`)); + } else { + resolve(stdout); + } + }); + }); + + const result: ICompletionResource[] = []; + for (const line of aliasOutput.split('\n')) { + const match = line.match(/^alias (?[a-zA-Z0-9\.:-]+) (?.+)$/); + if (!match?.groups) { + continue; + } + result.push({ + label: match.groups.alias, + detail: match.groups.resolved, + kind: vscode.TerminalCompletionItemKind.Alias, + }); + } + return result; +} diff --git a/extensions/terminal-suggest/src/shell/zsh.ts b/extensions/terminal-suggest/src/shell/zsh.ts index a19c71677ec1..d197dba9147e 100644 --- a/extensions/terminal-suggest/src/shell/zsh.ts +++ b/extensions/terminal-suggest/src/shell/zsh.ts @@ -9,12 +9,12 @@ import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_proc export async function getZshGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { return [ - ...await getZshBuiltins(options, existingCommands), - ...await getZshAliases(options), + ...await getAliases(options), + ...await getBuiltins(options, existingCommands), ]; } -async function getZshBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { +async function getBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { const compgenOutput = await new Promise((resolve, reject) => { exec('printf "%s\\n" ${(k)builtins}', options, (error, stdout) => { if (error) { @@ -28,7 +28,7 @@ async function getZshBuiltins(options: ExecOptionsWithStringEncoding, existingCo return compgenOutput.split('\n').filter(filter); } -async function getZshAliases(options: ExecOptionsWithStringEncoding): Promise { +async function getAliases(options: ExecOptionsWithStringEncoding): Promise { // This must be run with interactive, otherwise there's a good chance aliases won't // be set up. Note that this could differ from the actual aliases as it's a new bash // session, for the same reason this would not include aliases that are created diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 3b3b7286fd89..9e5547d05c1c 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import * as fs from 'fs/promises'; import * as path from 'path'; -import { exec, ExecOptionsWithStringEncoding, execSync } from 'child_process'; +import { exec, ExecOptionsWithStringEncoding } from 'child_process'; import { upstreamSpecs } from './constants'; import codeCompletionSpec from './completions/code'; import cdSpec from './completions/cd'; @@ -15,6 +15,7 @@ import { isExecutable } from './helpers/executable'; import type { ICompletionResource } from './types'; import { getBashGlobals } from './shell/bash'; import { getZshGlobals } from './shell/zsh'; +import { getFishGlobals } from './shell/fish'; const enum PwshCommandType { Alias = 1 @@ -60,8 +61,7 @@ async function getBuiltinCommands(shellType: TerminalShellType, existingCommands } case TerminalShellType.Fish: { // TODO: Ghost text in the command line prevents completions from working ATM for fish - const fishOutput = execSync('functions -n', options); - commands = fishOutput.split(', ').filter(filter); + commands = await getFishGlobals(options, existingCommands); break; } case TerminalShellType.PowerShell: { From 7e1c4edb7604e99970c85b1a42f37f0a24572e3e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 10:30:20 -0800 Subject: [PATCH 0253/2632] Remove compile warning --- extensions/terminal-suggest/src/terminalSuggestMain.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 9e5547d05c1c..faf868e3a6ce 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -43,7 +43,6 @@ async function getBuiltinCommands(shellType: TerminalShellType, existingCommands if (cachedCommands) { return cachedCommands; } - const filter = (cmd: string) => cmd && !existingCommands?.has(cmd); const shell = getShell(shellType); if (!shell) { return; From a1f8e8d54e62158ecaa66d4fc724d77ef62acdd3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 10:35:51 -0800 Subject: [PATCH 0254/2632] Use a map over a switch statement --- .../src/terminalSuggestMain.ts | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index faf868e3a6ce..cd6d1483320c 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -21,6 +21,23 @@ const enum PwshCommandType { Alias = 1 } +// TODO: remove once API is finalized +export enum TerminalShellType { + Sh = 1, + Bash = 2, + Fish = 3, + Csh = 4, + Ksh = 5, + Zsh = 6, + CommandPrompt = 7, + GitBash = 8, + PowerShell = 9, + Python = 10, + Julia = 11, + NuShell = 12, + Node = 13 +} + const isWindows = osIsWindows(); let cachedAvailableCommandsPath: string | undefined; let cachedWindowsExecutableExtensions: { [key: string]: boolean | undefined } | undefined; @@ -37,6 +54,14 @@ for (const spec of upstreamSpecs) { availableSpecs.push(require(`./completions/upstream/${spec}`).default); } +const getShellGlobals: Map) => Promise<(string | ICompletionResource)[]>> = new Map([ + [TerminalShellType.Bash, getBashGlobals], + [TerminalShellType.Zsh, getZshGlobals], + // TODO: Ghost text in the command line prevents completions from working ATM for fish + [TerminalShellType.Fish, getFishGlobals], + // [TerminalShellType.PowerShell]: getPwshGlobals, +]); + async function getBuiltinCommands(shellType: TerminalShellType, existingCommands?: Set): Promise { try { const cachedCommands = cachedBuiltinCommands.get(shellType); @@ -50,19 +75,6 @@ async function getBuiltinCommands(shellType: TerminalShellType, existingCommands const options: ExecOptionsWithStringEncoding = { encoding: 'utf-8', shell }; let commands: (string | ICompletionResource)[] | undefined; switch (shellType) { - case TerminalShellType.Bash: { - commands = await getBashGlobals(options, existingCommands); - break; - } - case TerminalShellType.Zsh: { - commands = await getZshGlobals(options, existingCommands); - break; - } - case TerminalShellType.Fish: { - // TODO: Ghost text in the command line prevents completions from working ATM for fish - commands = await getFishGlobals(options, existingCommands); - break; - } case TerminalShellType.PowerShell: { const output = await new Promise((resolve, reject) => { exec('Get-Command -All | Select-Object Name, CommandType, DisplayName, Definition | ConvertTo-Json', { @@ -103,6 +115,10 @@ async function getBuiltinCommands(shellType: TerminalShellType, existingCommands cachedBuiltinCommands.set(shellType, commandResources); return commandResources; } + default: { + commands = await getShellGlobals.get(shellType)?.(options, existingCommands); + break; + } } const commandResources = commands?.map(command => typeof command === 'string' ? ({ label: command }) : command); @@ -561,24 +577,6 @@ function getFriendlyResourcePath(uri: vscode.Uri, pathSeparator: string, kind?: return path; } -// TODO: remove once API is finalized -export enum TerminalShellType { - Sh = 1, - Bash = 2, - Fish = 3, - Csh = 4, - Ksh = 5, - Zsh = 6, - CommandPrompt = 7, - GitBash = 8, - PowerShell = 9, - Python = 10, - Julia = 11, - NuShell = 12, - Node = 13 -} - - function getShell(shellType: TerminalShellType): string | undefined { switch (shellType) { case TerminalShellType.Bash: From a3b6a78384d8f022cc2a81295b0959678ff2b201 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 31 Jan 2025 21:01:30 +0100 Subject: [PATCH 0255/2632] Opening a file without read permissions does not yield an error (fix #239298) (#239366) --- src/vs/platform/windows/electron-main/windowsMainService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index b47f1a17cb4f..d569ef654779 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1164,13 +1164,15 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic this.workspacesHistoryMainService.removeRecentlyOpened([fileUri]); // assume this is a file that does not yet exist - if (options.ignoreFileNotFound) { + if (options.ignoreFileNotFound && error.code === 'ENOENT') { return { fileUri, type: FileType.File, exists: false }; } + + this.logService.error(`Invalid path provided: ${path}, ${error.message}`); } return undefined; From 3e048f17b0c29e9ee72aceae94a6c92c72d36609 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 31 Jan 2025 12:44:55 -0800 Subject: [PATCH 0256/2632] Provide error message when it looks like node arch does not match the system (#239364) --- build/npm/preinstall.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js index 31821ee2393d..41a17b016767 100644 --- a/build/npm/preinstall.js +++ b/build/npm/preinstall.js @@ -23,6 +23,7 @@ if (process.env['npm_execpath'].includes('yarn')) { const path = require('path'); const fs = require('fs'); const cp = require('child_process'); +const os = require('os'); if (process.platform === 'win32') { if (!hasSupportedVisualStudioVersion()) { @@ -32,6 +33,11 @@ if (process.platform === 'win32') { installHeaders(); } +if (process.arch !== os.arch()) { + console.error(`\x1b[1;31m*** ARCHITECTURE MISMATCH: The node.js process is ${process.arch}, but your OS architecture is ${os.arch()}. ***\x1b[0;0m`); + console.error(`\x1b[1;31m*** This can greatly increase the build time of vs code. ***\x1b[0;0m`); +} + function hasSupportedVisualStudioVersion() { const fs = require('fs'); const path = require('path'); From 735fde9116c34acdc335187cb77a925029fdf51a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 12:52:34 -0800 Subject: [PATCH 0257/2632] Move pwsh logic into own file --- extensions/terminal-suggest/src/shell/pwsh.ts | 58 +++++++++++++++ .../src/terminalSuggestMain.ts | 70 +++---------------- 2 files changed, 68 insertions(+), 60 deletions(-) create mode 100644 extensions/terminal-suggest/src/shell/pwsh.ts diff --git a/extensions/terminal-suggest/src/shell/pwsh.ts b/extensions/terminal-suggest/src/shell/pwsh.ts new file mode 100644 index 000000000000..4daa0926b86c --- /dev/null +++ b/extensions/terminal-suggest/src/shell/pwsh.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import type { ICompletionResource } from '../types'; +import { exec, type ExecOptionsWithStringEncoding } from 'node:child_process'; + +export async function getPwshGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { + return [ + ...await getCommands(options, existingCommands), + ]; +} + +const enum PwshCommandType { + Alias = 1 +} + +async function getCommands(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { + + const output = await new Promise((resolve, reject) => { + exec('Get-Command -All | Select-Object Name, CommandType, DisplayName, Definition | ConvertTo-Json', { + ...options, + maxBuffer: 1024 * 1024 * 100 // This is a lot of content, increase buffer size + }, (error, stdout) => { + if (error) { + reject(error); + return; + } + resolve(stdout); + }); + }); + let json: any; + try { + json = JSON.parse(output); + } catch (e) { + console.error('Error parsing pwsh output:', e); + return []; + } + return (json as any[]).map(e => { + switch (e.CommandType) { + case PwshCommandType.Alias: { + return { + label: e.Name, + detail: e.DisplayName, + kind: vscode.TerminalCompletionItemKind.Alias, + }; + } + default: { + return { + label: e.Name, + detail: e.Definition, + }; + } + } + }); +} diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index cd6d1483320c..6ca278bcfb52 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -16,10 +16,7 @@ import type { ICompletionResource } from './types'; import { getBashGlobals } from './shell/bash'; import { getZshGlobals } from './shell/zsh'; import { getFishGlobals } from './shell/fish'; - -const enum PwshCommandType { - Alias = 1 -} +import { getPwshGlobals } from './shell/pwsh'; // TODO: remove once API is finalized export enum TerminalShellType { @@ -54,15 +51,15 @@ for (const spec of upstreamSpecs) { availableSpecs.push(require(`./completions/upstream/${spec}`).default); } -const getShellGlobals: Map) => Promise<(string | ICompletionResource)[]>> = new Map([ +const getShellSpecificGlobals: Map) => Promise<(string | ICompletionResource)[]>> = new Map([ [TerminalShellType.Bash, getBashGlobals], [TerminalShellType.Zsh, getZshGlobals], // TODO: Ghost text in the command line prevents completions from working ATM for fish [TerminalShellType.Fish, getFishGlobals], - // [TerminalShellType.PowerShell]: getPwshGlobals, + [TerminalShellType.PowerShell, getPwshGlobals], ]); -async function getBuiltinCommands(shellType: TerminalShellType, existingCommands?: Set): Promise { +async function getShellGlobals(shellType: TerminalShellType, existingCommands?: Set): Promise { try { const cachedCommands = cachedBuiltinCommands.get(shellType); if (cachedCommands) { @@ -73,57 +70,10 @@ async function getBuiltinCommands(shellType: TerminalShellType, existingCommands return; } const options: ExecOptionsWithStringEncoding = { encoding: 'utf-8', shell }; - let commands: (string | ICompletionResource)[] | undefined; - switch (shellType) { - case TerminalShellType.PowerShell: { - const output = await new Promise((resolve, reject) => { - exec('Get-Command -All | Select-Object Name, CommandType, DisplayName, Definition | ConvertTo-Json', { - ...options, - maxBuffer: 1024 * 1024 * 100 // This is a lot of content, increase buffer size - }, (error, stdout) => { - if (error) { - reject(error); - return; - } - resolve(stdout); - }); - }); - let json: any; - try { - json = JSON.parse(output); - } catch (e) { - console.error('Error parsing pwsh output:', e); - return []; - } - const commandResources = (json as any[]).map(e => { - switch (e.CommandType) { - case PwshCommandType.Alias: { - return { - label: e.Name, - detail: e.DisplayName, - kind: vscode.TerminalCompletionItemKind.Alias, - }; - } - default: { - return { - label: e.Name, - detail: e.Definition, - }; - } - } - }); - cachedBuiltinCommands.set(shellType, commandResources); - return commandResources; - } - default: { - commands = await getShellGlobals.get(shellType)?.(options, existingCommands); - break; - } - } - - const commandResources = commands?.map(command => typeof command === 'string' ? ({ label: command }) : command); - cachedBuiltinCommands.set(shellType, commandResources); - return commandResources; + const mixedCommands: (string | ICompletionResource)[] | undefined = await getShellSpecificGlobals.get(shellType)?.(options, existingCommands); + const normalizedCommands = mixedCommands?.map(command => typeof command === 'string' ? ({ label: command }) : command); + cachedBuiltinCommands.set(shellType, normalizedCommands); + return normalizedCommands; } catch (error) { console.error('Error fetching builtin commands:', error); @@ -145,11 +95,11 @@ export async function activate(context: vscode.ExtensionContext) { } const commandsInPath = await getCommandsInPath(terminal.shellIntegration?.env); - const builtinCommands = await getBuiltinCommands(shellType, commandsInPath?.labels) ?? []; + const shellGlobals = await getShellGlobals(shellType, commandsInPath?.labels) ?? []; if (!commandsInPath?.completionResources) { return; } - const commands = [...commandsInPath.completionResources, ...builtinCommands]; + const commands = [...commandsInPath.completionResources, ...shellGlobals]; const prefix = getPrefix(terminalContext.commandLine, terminalContext.cursorPosition); const pathSeparator = isWindows ? '\\' : '/'; From fee91cbff51248d85d76cfc2bea4a2252ac9da82 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 13:07:11 -0800 Subject: [PATCH 0258/2632] Share exec/spawn code between shells --- extensions/terminal-suggest/src/shell/bash.ts | 29 ++------------ .../terminal-suggest/src/shell/common.ts | 40 +++++++++++++++++++ extensions/terminal-suggest/src/shell/fish.ts | 28 ++----------- extensions/terminal-suggest/src/shell/pwsh.ts | 18 +++------ extensions/terminal-suggest/src/shell/zsh.ts | 29 ++------------ .../src/terminalSuggestMain.ts | 2 +- 6 files changed, 58 insertions(+), 88 deletions(-) create mode 100644 extensions/terminal-suggest/src/shell/common.ts diff --git a/extensions/terminal-suggest/src/shell/bash.ts b/extensions/terminal-suggest/src/shell/bash.ts index 70a8110eba3c..b440af3c8805 100644 --- a/extensions/terminal-suggest/src/shell/bash.ts +++ b/extensions/terminal-suggest/src/shell/bash.ts @@ -5,7 +5,8 @@ import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; -import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_process'; +import { type ExecOptionsWithStringEncoding } from 'node:child_process'; +import { execHelper, spawnHelper } from './common'; export async function getBashGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { return [ @@ -15,15 +16,7 @@ export async function getBashGlobals(options: ExecOptionsWithStringEncoding, exi } async function getBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { - const compgenOutput = await new Promise((resolve, reject) => { - exec('compgen -b', options, (error, stdout) => { - if (error) { - reject(error); - } else { - resolve(stdout); - } - }); - }); + const compgenOutput = await execHelper('compgen -b', options); const filter = (cmd: string) => cmd && !existingCommands?.has(cmd); return compgenOutput.split('\n').filter(filter); } @@ -33,21 +26,7 @@ async function getAliases(options: ExecOptionsWithStringEncoding): Promise((resolve, reject) => { - const child = spawn('bash', ['-ic', 'alias'], options); - let stdout = ''; - child.stdout.on('data', (data) => { - stdout += data; - }); - child.on('close', (code) => { - if (code !== 0) { - reject(new Error(`bash process exited with code ${code}`)); - } else { - resolve(stdout); - } - }); - }); - + const aliasOutput = await spawnHelper('bash', ['-ic', 'alias'], options); const result: ICompletionResource[] = []; for (const line of aliasOutput.split('\n')) { const match = line.match(/^alias (?[a-zA-Z0-9\.:-]+)='(?.+)'$/); diff --git a/extensions/terminal-suggest/src/shell/common.ts b/extensions/terminal-suggest/src/shell/common.ts new file mode 100644 index 000000000000..5bb5c349f2bc --- /dev/null +++ b/extensions/terminal-suggest/src/shell/common.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_process'; + +export async function spawnHelper(command: string, args: string[], options: ExecOptionsWithStringEncoding): Promise { + // This must be run with interactive, otherwise there's a good chance aliases won't + // be set up. Note that this could differ from the actual aliases as it's a new bash + // session, for the same reason this would not include aliases that are created + // by simply running `alias ...` in the terminal. + return new Promise((resolve, reject) => { + const child = spawn(command, args, options); + let stdout = ''; + child.stdout.on('data', (data) => { + stdout += data; + }); + child.on('close', (code) => { + if (code !== 0) { + reject(new Error(`bash process exited with code ${code}`)); + } else { + resolve(stdout); + } + }); + }); +} + +export async function execHelper(commandLine: string, options: ExecOptionsWithStringEncoding): Promise { + return new Promise((resolve, reject) => { + exec(commandLine, options, (error, stdout) => { + if (error) { + reject(error); + } else { + resolve(stdout); + } + }); + }); +} + diff --git a/extensions/terminal-suggest/src/shell/fish.ts b/extensions/terminal-suggest/src/shell/fish.ts index f63ec5adf4db..eaa472d2b273 100644 --- a/extensions/terminal-suggest/src/shell/fish.ts +++ b/extensions/terminal-suggest/src/shell/fish.ts @@ -5,7 +5,8 @@ import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; -import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_process'; +import { execHelper, spawnHelper } from './common'; +import { type ExecOptionsWithStringEncoding } from 'node:child_process'; export async function getFishGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { return [ @@ -15,15 +16,7 @@ export async function getFishGlobals(options: ExecOptionsWithStringEncoding, exi } async function getBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { - const compgenOutput = await new Promise((resolve, reject) => { - exec('functions -n', options, (error, stdout) => { - if (error) { - reject(error); - } else { - resolve(stdout); - } - }); - }); + const compgenOutput = await execHelper('functions -n', options); const filter = (cmd: string) => cmd && !existingCommands?.has(cmd); return compgenOutput.split(', ').filter(filter); } @@ -33,20 +26,7 @@ async function getAliases(options: ExecOptionsWithStringEncoding): Promise((resolve, reject) => { - const child = spawn('fish', ['-ic', 'alias'], options); - let stdout = ''; - child.stdout.on('data', (data) => { - stdout += data; - }); - child.on('close', (code) => { - if (code !== 0) { - reject(new Error(`bash process exited with code ${code}`)); - } else { - resolve(stdout); - } - }); - }); + const aliasOutput = await spawnHelper('fish', ['-ic', 'alias'], options); const result: ICompletionResource[] = []; for (const line of aliasOutput.split('\n')) { diff --git a/extensions/terminal-suggest/src/shell/pwsh.ts b/extensions/terminal-suggest/src/shell/pwsh.ts index 4daa0926b86c..47b8e7fe7f63 100644 --- a/extensions/terminal-suggest/src/shell/pwsh.ts +++ b/extensions/terminal-suggest/src/shell/pwsh.ts @@ -5,7 +5,8 @@ import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; -import { exec, type ExecOptionsWithStringEncoding } from 'node:child_process'; +import { type ExecOptionsWithStringEncoding } from 'node:child_process'; +import { execHelper } from './common'; export async function getPwshGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { return [ @@ -18,18 +19,9 @@ const enum PwshCommandType { } async function getCommands(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { - - const output = await new Promise((resolve, reject) => { - exec('Get-Command -All | Select-Object Name, CommandType, DisplayName, Definition | ConvertTo-Json', { - ...options, - maxBuffer: 1024 * 1024 * 100 // This is a lot of content, increase buffer size - }, (error, stdout) => { - if (error) { - reject(error); - return; - } - resolve(stdout); - }); + const output = await execHelper('Get-Command -All | Select-Object Name, CommandType, DisplayName, Definition | ConvertTo-Json', { + ...options, + maxBuffer: 1024 * 1024 * 100 // This is a lot of content, increase buffer size }); let json: any; try { diff --git a/extensions/terminal-suggest/src/shell/zsh.ts b/extensions/terminal-suggest/src/shell/zsh.ts index d197dba9147e..ce00edb900ba 100644 --- a/extensions/terminal-suggest/src/shell/zsh.ts +++ b/extensions/terminal-suggest/src/shell/zsh.ts @@ -5,7 +5,8 @@ import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; -import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_process'; +import { execHelper, spawnHelper } from './common'; +import { type ExecOptionsWithStringEncoding } from 'node:child_process'; export async function getZshGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { return [ @@ -15,15 +16,7 @@ export async function getZshGlobals(options: ExecOptionsWithStringEncoding, exis } async function getBuiltins(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { - const compgenOutput = await new Promise((resolve, reject) => { - exec('printf "%s\\n" ${(k)builtins}', options, (error, stdout) => { - if (error) { - reject(error); - } else { - resolve(stdout); - } - }); - }); + const compgenOutput = await execHelper('printf "%s\\n" ${(k)builtins}', options); const filter = (cmd: string) => cmd && !existingCommands?.has(cmd); return compgenOutput.split('\n').filter(filter); } @@ -33,21 +26,7 @@ async function getAliases(options: ExecOptionsWithStringEncoding): Promise((resolve, reject) => { - const child = spawn('zsh', ['-ic', 'alias'], options); - let stdout = ''; - child.stdout.on('data', (data) => { - stdout += data; - }); - child.on('close', (code) => { - if (code !== 0) { - reject(new Error(`zsh process exited with code ${code}`)); - } else { - resolve(stdout); - } - }); - }); - + const aliasOutput = await spawnHelper('zsh', ['-ic', 'alias'], options); const result: ICompletionResource[] = []; for (const line of aliasOutput.split('\n')) { const match = line.match(/^(?[a-zA-Z0-9\.:-]+)=(?:'(?.+)'|(?.+))$/); diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 6ca278bcfb52..b348e7a73fc7 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import * as fs from 'fs/promises'; import * as path from 'path'; -import { exec, ExecOptionsWithStringEncoding } from 'child_process'; +import { ExecOptionsWithStringEncoding } from 'child_process'; import { upstreamSpecs } from './constants'; import codeCompletionSpec from './completions/code'; import cdSpec from './completions/cd'; From 6540b40a3d42024b2643d8ba005f103cdebd9b92 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 13:27:26 -0800 Subject: [PATCH 0259/2632] Set kind for all pwsh commands, handle aliases without resolved --- extensions/terminal-suggest/src/shell/pwsh.ts | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/extensions/terminal-suggest/src/shell/pwsh.ts b/extensions/terminal-suggest/src/shell/pwsh.ts index 47b8e7fe7f63..8751b3f2af76 100644 --- a/extensions/terminal-suggest/src/shell/pwsh.ts +++ b/extensions/terminal-suggest/src/shell/pwsh.ts @@ -14,10 +14,46 @@ export async function getPwshGlobals(options: ExecOptionsWithStringEncoding, exi ]; } +/** + * The numeric values associated with CommandType from Get-Command. It appears that this is a + * bitfield based on the values but I think it's actually used as an enum where a CommandType can + * only be a single one of these. + * + * Source: + * + * ``` + * [enum]::GetValues([System.Management.Automation.CommandTypes]) | ForEach-Object { + * [pscustomobject]@{ + * Name = $_ + * Value = [int]$_ + * } + * } + * ``` + */ const enum PwshCommandType { - Alias = 1 + Alias = 1, + Function = 2, + Filter = 4, + Cmdlet = 8, + ExternalScript = 16, + Application = 32, + Script = 64, + Configuration = 256, + // All = 383, } +const pwshCommandTypeToCompletionKind: Map = new Map([ + [PwshCommandType.Alias, vscode.TerminalCompletionItemKind.Alias], + [PwshCommandType.Function, vscode.TerminalCompletionItemKind.Method], + [PwshCommandType.Filter, vscode.TerminalCompletionItemKind.Method], + [PwshCommandType.Cmdlet, vscode.TerminalCompletionItemKind.Method], + [PwshCommandType.ExternalScript, vscode.TerminalCompletionItemKind.Method], + [PwshCommandType.Application, vscode.TerminalCompletionItemKind.Method], + [PwshCommandType.Script, vscode.TerminalCompletionItemKind.Method], + [PwshCommandType.Configuration, vscode.TerminalCompletionItemKind.Argument], +]); + + async function getCommands(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { const output = await execHelper('Get-Command -All | Select-Object Name, CommandType, DisplayName, Definition | ConvertTo-Json', { ...options, @@ -36,13 +72,17 @@ async function getCommands(options: ExecOptionsWithStringEncoding, existingComma return { label: e.Name, detail: e.DisplayName, - kind: vscode.TerminalCompletionItemKind.Alias, + // Aliases sometimes return the same DisplayName, show as a method in this case. + kind: (e.Name === e.DisplayName + ? vscode.TerminalCompletionItemKind.Method + : vscode.TerminalCompletionItemKind.Alias), }; } default: { return { label: e.Name, detail: e.Definition, + kind: pwshCommandTypeToCompletionKind.get(e.CommandType) }; } } From 0baef062361c764793fe714ba850b4ebfffd4d43 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 31 Jan 2025 16:03:21 -0600 Subject: [PATCH 0260/2632] ensure space after command before providing options (#239378) fix #239249 --- extensions/terminal-suggest/src/terminalSuggestMain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index db52610be6c9..69d474247fed 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -355,7 +355,7 @@ export async function getCompletionItemsFromSpecs( continue; } - if (!terminalContext.commandLine.startsWith(specLabel)) { + if (!terminalContext.commandLine.startsWith(`${specLabel} `)) { // the spec label is not the first word in the command line, so do not provide options or args continue; } From 92f0b764f84bb5c4c5495266e12fca80e47bba86 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 14:04:33 -0800 Subject: [PATCH 0261/2632] Polish format of aliases --- .../terminal-suggest/src/shell/common.ts | 2 +- extensions/terminal-suggest/src/shell/pwsh.ts | 62 ++++++++++++------- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/extensions/terminal-suggest/src/shell/common.ts b/extensions/terminal-suggest/src/shell/common.ts index 5bb5c349f2bc..590195e3600b 100644 --- a/extensions/terminal-suggest/src/shell/common.ts +++ b/extensions/terminal-suggest/src/shell/common.ts @@ -18,7 +18,7 @@ export async function spawnHelper(command: string, args: string[], options: Exec }); child.on('close', (code) => { if (code !== 0) { - reject(new Error(`bash process exited with code ${code}`)); + reject(new Error(`process exited with code ${code}`)); } else { resolve(stdout); } diff --git a/extensions/terminal-suggest/src/shell/pwsh.ts b/extensions/terminal-suggest/src/shell/pwsh.ts index 8751b3f2af76..ed6d9f50d94a 100644 --- a/extensions/terminal-suggest/src/shell/pwsh.ts +++ b/extensions/terminal-suggest/src/shell/pwsh.ts @@ -10,6 +10,7 @@ import { execHelper } from './common'; export async function getPwshGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { return [ + ...await getAliases(options, existingCommands), ...await getCommands(options, existingCommands), ]; } @@ -53,6 +54,37 @@ const pwshCommandTypeToCompletionKind: Map): Promise { + const output = await execHelper('Get-Command -CommandType Alias | Select-Object Name, CommandType, Definition, ModuleName, @{Name="Version";Expression={$_.Version.ToString()}} | ConvertTo-Json', { + ...options, + maxBuffer: 1024 * 1024 * 100 // This is a lot of content, increase buffer size + }); + let json: any; + try { + json = JSON.parse(output); + } catch (e) { + console.error('Error parsing output:', e); + return []; + } + return (json as any[]).map(e => { + const detailParts: string[] = []; + if (e.Definition) { + detailParts.push(e.Definition); + } + if (e.ModuleName && e.Version) { + detailParts.push(`${e.ModuleName} v${e.Version}`); + } + return { + label: e.Name, + detail: detailParts.join('\n\n'), + // Aliases sometimes return the same DisplayName, show as a method in this case. + kind: (e.Name === e.DisplayName + ? vscode.TerminalCompletionItemKind.Method + : vscode.TerminalCompletionItemKind.Alias), + }; + } + ); +} async function getCommands(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { const output = await execHelper('Get-Command -All | Select-Object Name, CommandType, DisplayName, Definition | ConvertTo-Json', { @@ -66,25 +98,13 @@ async function getCommands(options: ExecOptionsWithStringEncoding, existingComma console.error('Error parsing pwsh output:', e); return []; } - return (json as any[]).map(e => { - switch (e.CommandType) { - case PwshCommandType.Alias: { - return { - label: e.Name, - detail: e.DisplayName, - // Aliases sometimes return the same DisplayName, show as a method in this case. - kind: (e.Name === e.DisplayName - ? vscode.TerminalCompletionItemKind.Method - : vscode.TerminalCompletionItemKind.Alias), - }; - } - default: { - return { - label: e.Name, - detail: e.Definition, - kind: pwshCommandTypeToCompletionKind.get(e.CommandType) - }; - } - } - }); + return ( + (json as any[]) + .filter(e => e.CommandType !== PwshCommandType.Alias) + .map(e => ({ + label: e.Name, + detail: e.Definition, + kind: pwshCommandTypeToCompletionKind.get(e.CommandType) + })) + ); } From c69d72c5159982907deca62378bd94f87096972b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 14:05:57 -0800 Subject: [PATCH 0262/2632] Correct non-definition entries --- extensions/terminal-suggest/src/shell/pwsh.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/extensions/terminal-suggest/src/shell/pwsh.ts b/extensions/terminal-suggest/src/shell/pwsh.ts index ed6d9f50d94a..d7a3a1954acd 100644 --- a/extensions/terminal-suggest/src/shell/pwsh.ts +++ b/extensions/terminal-suggest/src/shell/pwsh.ts @@ -77,10 +77,11 @@ async function getAliases(options: ExecOptionsWithStringEncoding, existingComman return { label: e.Name, detail: detailParts.join('\n\n'), - // Aliases sometimes return the same DisplayName, show as a method in this case. - kind: (e.Name === e.DisplayName - ? vscode.TerminalCompletionItemKind.Method - : vscode.TerminalCompletionItemKind.Alias), + // Aliases sometimes don't have a definition and use the same DisplayName, show them as + // a method in this case. + kind: (!e.Definition + ? vscode.TerminalCompletionItemKind.Alias + : vscode.TerminalCompletionItemKind.Method), }; } ); From 9802b181393a1c82b7115cd9d41f4e4902a5c35e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 14:35:40 -0800 Subject: [PATCH 0263/2632] Make other command types consistent, polish abnormal alias types --- extensions/terminal-suggest/src/shell/pwsh.ts | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/extensions/terminal-suggest/src/shell/pwsh.ts b/extensions/terminal-suggest/src/shell/pwsh.ts index d7a3a1954acd..f3b0e371573b 100644 --- a/extensions/terminal-suggest/src/shell/pwsh.ts +++ b/extensions/terminal-suggest/src/shell/pwsh.ts @@ -55,7 +55,7 @@ const pwshCommandTypeToCompletionKind: Map): Promise { - const output = await execHelper('Get-Command -CommandType Alias | Select-Object Name, CommandType, Definition, ModuleName, @{Name="Version";Expression={$_.Version.ToString()}} | ConvertTo-Json', { + const output = await execHelper('Get-Command -CommandType Alias | Select-Object Name, CommandType, Definition, DisplayName, ModuleName, @{Name="Version";Expression={$_.Version.ToString()}} | ConvertTo-Json', { ...options, maxBuffer: 1024 * 1024 * 100 // This is a lot of content, increase buffer size }); @@ -67,9 +67,11 @@ async function getAliases(options: ExecOptionsWithStringEncoding, existingComman return []; } return (json as any[]).map(e => { + // Aliases sometimes use the same Name and DisplayName, show them as methods in this case. + const isAlias = e.Name !== e.DisplayName; const detailParts: string[] = []; if (e.Definition) { - detailParts.push(e.Definition); + detailParts.push(isAlias ? `→ ${e.Definition}` : e.Definition); } if (e.ModuleName && e.Version) { detailParts.push(`${e.ModuleName} v${e.Version}`); @@ -77,18 +79,15 @@ async function getAliases(options: ExecOptionsWithStringEncoding, existingComman return { label: e.Name, detail: detailParts.join('\n\n'), - // Aliases sometimes don't have a definition and use the same DisplayName, show them as - // a method in this case. - kind: (!e.Definition + kind: (isAlias ? vscode.TerminalCompletionItemKind.Alias : vscode.TerminalCompletionItemKind.Method), }; - } - ); + }); } async function getCommands(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise { - const output = await execHelper('Get-Command -All | Select-Object Name, CommandType, DisplayName, Definition | ConvertTo-Json', { + const output = await execHelper('Get-Command -All | Select-Object Name, CommandType, Definition, ModuleName, @{Name="Version";Expression={$_.Version.ToString()}} | ConvertTo-Json', { ...options, maxBuffer: 1024 * 1024 * 100 // This is a lot of content, increase buffer size }); @@ -102,10 +101,19 @@ async function getCommands(options: ExecOptionsWithStringEncoding, existingComma return ( (json as any[]) .filter(e => e.CommandType !== PwshCommandType.Alias) - .map(e => ({ - label: e.Name, - detail: e.Definition, - kind: pwshCommandTypeToCompletionKind.get(e.CommandType) - })) + .map(e => { + const detailParts: string[] = []; + if (e.Definition) { + detailParts.push(e.Definition.trim()); + } + if (e.ModuleName && e.Version) { + detailParts.push(`${e.ModuleName} v${e.Version}`); + } + return { + label: e.Name, + detail: detailParts.join('\n\n'), + kind: pwshCommandTypeToCompletionKind.get(e.CommandType) + }; + }) ); } From 2aaf62826970e2cef80e9e90e61d8f5a1a89b235 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 31 Jan 2025 16:36:47 -0600 Subject: [PATCH 0264/2632] fix missing/incorrect info in editor and edits a11y help dialogs (#239377) fix #239325 --- src/vs/editor/common/standaloneStrings.ts | 2 +- .../contrib/chat/browser/actions/chatAccessibilityHelp.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index f2edfd33f60a..c181573190ba 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -36,7 +36,7 @@ export namespace AccessibilityHelpNLS { export const debugExecuteSelection = nls.localize('debugConsole.executeSelection', "The Debug: Execute Selection command{0} will execute the selected text in the debug console.", ''); export const chatEditorModification = nls.localize('chatEditorModification', "The editor contains pending modifications that have been made by chat."); export const chatEditorRequestInProgress = nls.localize('chatEditorRequestInProgress', "The editor is currently waiting for modifications to be made by chat."); - export const chatEditActions = nls.localize('chatEditing.navigation', 'Navigate between edits in the editor with navigate previous{0} and next{1} and accept{3} and reject the current change{4}.', '', '', '', ''); + export const chatEditActions = nls.localize('chatEditing.navigation', 'Navigate between edits in the editor with navigate previous{0} and next{1} and accept{2}, reject{3} or view the diff{4} for the current change.', '', '', '', '', ''); } export namespace InspectTokensNLS { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 5ed43c1ba433..bc485108df65 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -79,7 +79,7 @@ export function getAccessibilityHelpText(type: 'panelChat' | 'inlineChat' | 'qui content.push(localize('chatEditing.expectation', 'When a request is made, a progress indicator will play while the edits are being applied.')); content.push(localize('chatEditing.review', 'Once the edits are applied, focus the editor(s) to review, accept, and discard changes.')); content.push(localize('chatEditing.sections', 'Navigate between edits in the editor with navigate previous{0} and next{1}', '', '')); - content.push(localize('chatEditing.acceptHunk', 'In the editor, Accept{0} and Reject the current Change{1}.', '', '')); + content.push(localize('chatEditing.acceptHunk', 'In the editor, Accept{0}, Reject{1}, or Toggle the Diff{2} for the current Change.', '', '', '')); content.push(localize('chatEditing.helpfulCommands', 'When in the edits view, some helpful commands include:')); content.push(localize('workbench.action.chat.undoEdits', '- Undo Edits{0}.', '')); content.push(localize('workbench.action.chat.editing.attachFiles', '- Attach Files{0}.', '')); From 9d7b58991320ff8fa17fc9aca1290980577b2df4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Fri, 31 Jan 2025 14:40:12 -0800 Subject: [PATCH 0265/2632] Remove arrow from alias detail --- extensions/terminal-suggest/src/shell/pwsh.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/terminal-suggest/src/shell/pwsh.ts b/extensions/terminal-suggest/src/shell/pwsh.ts index f3b0e371573b..321e7f5b54b7 100644 --- a/extensions/terminal-suggest/src/shell/pwsh.ts +++ b/extensions/terminal-suggest/src/shell/pwsh.ts @@ -71,7 +71,7 @@ async function getAliases(options: ExecOptionsWithStringEncoding, existingComman const isAlias = e.Name !== e.DisplayName; const detailParts: string[] = []; if (e.Definition) { - detailParts.push(isAlias ? `→ ${e.Definition}` : e.Definition); + detailParts.push(e.Definition); } if (e.ModuleName && e.Version) { detailParts.push(`${e.ModuleName} v${e.Version}`); From 4c32889faf1e3c533dc024b1bfdb2d78b08888e7 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 31 Jan 2025 21:26:18 -0800 Subject: [PATCH 0266/2632] Show InputBox for unsupported clients (#239389) * Show InputBox for unsupported clients Fixes https://github.com/microsoft/vscode/issues/238147 * comment * Add 127.0.0.1 for good measure --- .../src/common/async.ts | 66 +++++++++++ .../src/common/env.ts | 30 +++++ .../src/common/loopbackClientAndOpener.ts | 112 ++++++++++++++++-- 3 files changed, 195 insertions(+), 13 deletions(-) create mode 100644 extensions/microsoft-authentication/src/common/env.ts diff --git a/extensions/microsoft-authentication/src/common/async.ts b/extensions/microsoft-authentication/src/common/async.ts index 3555bb095026..5f02cc5976d6 100644 --- a/extensions/microsoft-authentication/src/common/async.ts +++ b/extensions/microsoft-authentication/src/common/async.ts @@ -111,3 +111,69 @@ function once(event: Event): Event { export function toPromise(event: Event): Promise { return new Promise(resolve => once(event)(resolve)); } + +//#region DeferredPromise + +export type ValueCallback = (value: T | Promise) => void; + +const enum DeferredOutcome { + Resolved, + Rejected +} + +/** + * Creates a promise whose resolution or rejection can be controlled imperatively. + */ +export class DeferredPromise { + + private completeCallback!: ValueCallback; + private errorCallback!: (err: unknown) => void; + private outcome?: { outcome: DeferredOutcome.Rejected; value: any } | { outcome: DeferredOutcome.Resolved; value: T }; + + public get isRejected() { + return this.outcome?.outcome === DeferredOutcome.Rejected; + } + + public get isResolved() { + return this.outcome?.outcome === DeferredOutcome.Resolved; + } + + public get isSettled() { + return !!this.outcome; + } + + public get value() { + return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined; + } + + public readonly p: Promise; + + constructor() { + this.p = new Promise((c, e) => { + this.completeCallback = c; + this.errorCallback = e; + }); + } + + public complete(value: T) { + return new Promise(resolve => { + this.completeCallback(value); + this.outcome = { outcome: DeferredOutcome.Resolved, value }; + resolve(); + }); + } + + public error(err: unknown) { + return new Promise(resolve => { + this.errorCallback(err); + this.outcome = { outcome: DeferredOutcome.Rejected, value: err }; + resolve(); + }); + } + + public cancel() { + return this.error(new CancellationError()); + } +} + +//#endregion diff --git a/extensions/microsoft-authentication/src/common/env.ts b/extensions/microsoft-authentication/src/common/env.ts new file mode 100644 index 000000000000..5d19183e70cb --- /dev/null +++ b/extensions/microsoft-authentication/src/common/env.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { Uri } from 'vscode'; + +const VALID_DESKTOP_CALLBACK_SCHEMES = [ + 'vscode', + 'vscode-insiders', + // On Windows, some browsers don't seem to redirect back to OSS properly. + // As a result, you get stuck in the auth flow. We exclude this from the + // list until we can figure out a way to fix this behavior in browsers. + // 'code-oss', + 'vscode-wsl', + 'vscode-exploration' +]; + +export function isSupportedClient(uri: Uri): boolean { + return ( + VALID_DESKTOP_CALLBACK_SCHEMES.includes(uri.scheme) || + // vscode.dev & insiders.vscode.dev + /(?:^|\.)vscode\.dev$/.test(uri.authority) || + // github.dev & codespaces + /(?:^|\.)github\.dev$/.test(uri.authority) || + // localhost + /^localhost:\d+$/.test(uri.authority) || + // 127.0.0.1 + /^127\.0\.0\.1:\d+$/.test(uri.authority) + ); +} diff --git a/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts b/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts index 3fbb03400379..e68663efe43a 100644 --- a/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts +++ b/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts @@ -5,14 +5,17 @@ import type { ILoopbackClient, ServerAuthorizationCodeResponse } from '@azure/msal-node'; import type { UriEventHandler } from '../UriEventHandler'; -import { env, LogOutputChannel, Uri } from 'vscode'; -import { toPromise } from './async'; +import { Disposable, env, l10n, LogOutputChannel, Uri, window } from 'vscode'; +import { DeferredPromise, toPromise } from './async'; +import { isSupportedClient } from './env'; export interface ILoopbackClientAndOpener extends ILoopbackClient { openBrowser(url: string): Promise; } export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener { + private _responseDeferred: DeferredPromise | undefined; + constructor( private readonly _uriHandler: UriEventHandler, private readonly _redirectUri: string, @@ -20,17 +23,14 @@ export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener { ) { } async listenForAuthCode(): Promise { - const url = await toPromise(this._uriHandler.event); - this._logger.debug(`Received URL event. Authority: ${url.authority}`); - const result = new URL(url.toString(true)); - - return { - code: result.searchParams.get('code') ?? undefined, - state: result.searchParams.get('state') ?? undefined, - error: result.searchParams.get('error') ?? undefined, - error_description: result.searchParams.get('error_description') ?? undefined, - error_uri: result.searchParams.get('error_uri') ?? undefined, - }; + await this._responseDeferred?.cancel(); + this._responseDeferred = new DeferredPromise(); + const result = await this._responseDeferred.p; + this._responseDeferred = undefined; + if (result) { + return result; + } + throw new Error('No valid response received for authorization code.'); } getRedirectUri(): string { @@ -46,7 +46,93 @@ export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener { async openBrowser(url: string): Promise { const callbackUri = await env.asExternalUri(Uri.parse(`${env.uriScheme}://vscode.microsoft-authentication`)); + if (isSupportedClient(callbackUri)) { + void this._getCodeResponseFromUriHandler(); + } else { + // Unsupported clients will be shown the code in the browser, but it will not redirect back since this + // isn't a supported client. Instead, they will copy that code in the browser and paste it in an input box + // that will be shown to them by the extension. + void this._getCodeResponseFromQuickPick(); + } + const uri = Uri.parse(url + `&state=${encodeURI(callbackUri.toString(true))}`); await env.openExternal(uri); } + + private async _getCodeResponseFromUriHandler(): Promise { + if (!this._responseDeferred) { + throw new Error('No listener for auth code'); + } + const url = await toPromise(this._uriHandler.event); + this._logger.debug(`Received URL event. Authority: ${url.authority}`); + const result = new URL(url.toString(true)); + + this._responseDeferred?.complete({ + code: result.searchParams.get('code') ?? undefined, + state: result.searchParams.get('state') ?? undefined, + error: result.searchParams.get('error') ?? undefined, + error_description: result.searchParams.get('error_description') ?? undefined, + error_uri: result.searchParams.get('error_uri') ?? undefined, + }); + } + + private async _getCodeResponseFromQuickPick(): Promise { + if (!this._responseDeferred) { + throw new Error('No listener for auth code'); + } + const inputBox = window.createInputBox(); + inputBox.ignoreFocusOut = true; + inputBox.title = l10n.t('Microsoft Authentication'); + inputBox.prompt = l10n.t('Provide the authorization code to complete the sign in flow.'); + inputBox.placeholder = l10n.t('Paste authorization code here...'); + inputBox.show(); + const code = await new Promise((resolve) => { + let resolvedValue: string | undefined = undefined; + const disposable = Disposable.from( + inputBox, + inputBox.onDidAccept(async () => { + if (!inputBox.value) { + inputBox.validationMessage = l10n.t('Authorization code is required.'); + return; + } + const code = inputBox.value; + resolvedValue = code; + resolve(code); + inputBox.hide(); + }), + inputBox.onDidChangeValue(() => { + inputBox.validationMessage = undefined; + }), + inputBox.onDidHide(() => { + disposable.dispose(); + if (!resolvedValue) { + resolve(undefined); + } + }) + ); + Promise.allSettled([this._responseDeferred?.p]).then(() => disposable.dispose()); + }); + // Something canceled the original deferred promise, so just return. + if (this._responseDeferred.isSettled) { + return; + } + if (code) { + this._logger.debug('Received auth code from quick pick'); + this._responseDeferred.complete({ + code, + state: undefined, + error: undefined, + error_description: undefined, + error_uri: undefined + }); + return; + } + this._responseDeferred.complete({ + code: undefined, + state: undefined, + error: 'User cancelled', + error_description: 'User cancelled', + error_uri: undefined + }); + } } From d487556d4d46366cba4684ca933df6b9d6824201 Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Sat, 1 Feb 2025 01:00:37 -0800 Subject: [PATCH 0267/2632] prompt files config description update (#239279) * [config]: improve setting description and add examples * [config]: fix localization ID compilation issue * [config]: update the `Examples` header level to h4 --- .../contrib/chat/browser/chat.contribution.ts | 11 ++--- .../chat/common/promptSyntax/config.ts | 45 ++++++++++++++++--- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index cf1d9b2ebf81..269b6536eab8 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -154,14 +154,9 @@ configurationRegistry.registerConfiguration({ default: true }, [PromptFilesConfig.CONFIG_KEY]: { - type: ['string', 'array', 'object', 'boolean', 'null'], - title: nls.localize('chat.promptFiles.setting.title', "Prompt Files"), - markdownDescription: nls.localize( - 'chat.promptFiles.setting.markdownDescription', - "Enable support for attaching reusable prompt files (`*{0}`) for Chat, Edits, and Inline Chat sessions. [Learn More]({1}).", - '.prompt.md', - PromptFilesConfig.DOCUMENTATION_URL, - ), + type: ['boolean', 'object', 'array', 'string', 'null'], + title: PromptFilesConfig.CONFIG_TITLE, + markdownDescription: PromptFilesConfig.CONFIG_DESCRIPTION, default: null, tags: ['experimental'], }, diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/config.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/config.ts index 1fa52b8fea9b..51eb7333f07a 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/config.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/config.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from '../../../../../nls.js'; +import { PROMPT_SNIPPET_FILE_EXTENSION } from './contentProviders/promptContentsProviderBase.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; /** @@ -21,21 +23,21 @@ import { IConfigurationService } from '../../../../../platform/configuration/com * (see {@link DEFAULT_LOCATION}): * ```json * { - * "chat.experimental.promptSnippets": true, + * "chat.promptFiles": true, * } * ``` * * Enable the feature, specifying a single prompt files source folder location: * ```json * { - * "chat.experimental.promptSnippets": '.github/prompts', + * "chat.promptFiles": '.github/prompts', * } * ``` * * Enable the feature, specifying multiple prompt files source folder location: * ```json * { - * "chat.experimental.promptSnippets": { + * "chat.promptFiles": { * ".github/prompts" : true, * ".copilot/prompts" : false, * "/Users/legomushroom/repos/prompts" : true, @@ -46,7 +48,7 @@ import { IConfigurationService } from '../../../../../platform/configuration/com * Enable the feature, specifying multiple prompt files source folder location: * ```json * { - * "chat.experimental.promptSnippets": [ + * "chat.promptFiles": [ * ".github/prompts", * ".copilot/prompts", * "/Users/legomushroom/repos/prompts", @@ -64,7 +66,7 @@ import { IConfigurationService } from '../../../../../platform/configuration/com * (see {@link DEFAULT_LOCATION}): * ```json * { - * "chat.experimental.promptSnippets": {}, + * "chat.promptFiles": {}, * } * ``` * @@ -213,6 +215,39 @@ export namespace PromptFilesConfig { return DEFAULT_LOCATION; }; + + const usageExample1 = nls.localize( + `chat.promptFiles.config.description.example1`, + "Enable with the default location of the prompt files (`{0}`):\n{1}", + DEFAULT_LOCATION[0], + `\`\`\`json\n{\n "${CONFIG_KEY}": true,\n}\n\`\`\``, + ); + const usageExample2 = nls.localize( + `chat.promptFiles.config.description.example2`, + "Specify custom location(s) of the prompt files:\n{0}", + `\`\`\`json\n{\n "${CONFIG_KEY}": {\n ".github/prompts": true,\n "/Users/vscode/prompts": true,\n}\n\`\`\``, + ); + + /** + * Configuration setting description to use in the settings UI. + */ + export const CONFIG_DESCRIPTION = nls.localize( + 'chat.promptFiles.config.description', + "Enable support for attaching reusable prompt files (`*{0}`) for Chat, Edits, and Inline Chat sessions. [Learn More]({1}).\n\nSet to `true` or use the `{ \"/path/to/folder\": boolean }` notation to specify a different path (or a couple of them). Relative paths are resolved from the root folder(s) of your workspace, and the default value of `{2}` is used if no other paths provided.\n#### Examples\n{3}\n{4}", + PROMPT_SNIPPET_FILE_EXTENSION, + DOCUMENTATION_URL, + DEFAULT_LOCATION[0], + usageExample1, + usageExample2, + ); + + /** + * Configuration setting title to use in the settings UI. + */ + export const CONFIG_TITLE = nls.localize( + `chat.promptFiles.config.title`, + "Prompt Files", + ); } /** From 9f753c77d9d3e49a2d367f965999282f3519b87f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 04:42:12 -0800 Subject: [PATCH 0268/2632] Simplify terminal suggest token variable Follow up on #239344 --- extensions/terminal-suggest/src/tokens.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/terminal-suggest/src/tokens.ts b/extensions/terminal-suggest/src/tokens.ts index 9712d2b0bfee..a967aeb20545 100644 --- a/extensions/terminal-suggest/src/tokens.ts +++ b/extensions/terminal-suggest/src/tokens.ts @@ -23,8 +23,7 @@ export function getTokenType(ctx: { commandLine: string; cursorPosition: number if (spaceIndex === -1) { return TokenType.Command; } - const tokenIndex = spaceIndex === -1 ? 0 : spaceIndex + 1; - const previousTokens = ctx.commandLine.substring(0, tokenIndex).trim(); + const previousTokens = ctx.commandLine.substring(0, spaceIndex + 1).trim(); const commandResetChars = shellType === undefined ? defaultShellTypeResetChars : shellTypeResetChars[shellType] ?? defaultShellTypeResetChars; if (commandResetChars.some(e => previousTokens.endsWith(e))) { return TokenType.Command; From fafd6b594a59f86ce8555547cdf422e324b66d12 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 05:17:57 -0800 Subject: [PATCH 0269/2632] Optimize getCommandsInPath, restore cache Changes: - Fix cache mechanism as pathValue never got stored - Cache labels instead of returning empty set - Walking each directory in separate promise and await via Promise.all - Don't create a promise on Windows when checking exe Fixes #239396 --- .../src/helpers/executable.ts | 6 +- .../src/terminalSuggestMain.ts | 61 +++++++++++++------ 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/extensions/terminal-suggest/src/helpers/executable.ts b/extensions/terminal-suggest/src/helpers/executable.ts index cd8fce4f8dc8..00f56f09cbda 100644 --- a/extensions/terminal-suggest/src/helpers/executable.ts +++ b/extensions/terminal-suggest/src/helpers/executable.ts @@ -6,11 +6,15 @@ import { osIsWindows } from './os'; import * as fs from 'fs/promises'; -export async function isExecutable(filePath: string, configuredWindowsExecutableExtensions?: { [key: string]: boolean | undefined } | undefined): Promise { +export function isExecutable(filePath: string, configuredWindowsExecutableExtensions?: { [key: string]: boolean | undefined } | undefined): Promise | boolean { if (osIsWindows()) { const resolvedWindowsExecutableExtensions = resolveWindowsExecutableExtensions(configuredWindowsExecutableExtensions); return resolvedWindowsExecutableExtensions.find(ext => filePath.endsWith(ext)) !== undefined; } + return isExecutableUnix(filePath); +} + +export async function isExecutableUnix(filePath: string): Promise { try { const stats = await fs.stat(filePath); // On macOS/Linux, check if the executable bit is set diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 82c00c2196a1..8d278d1c002d 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -41,6 +41,7 @@ let cachedAvailableCommandsPath: string | undefined; let cachedWindowsExecutableExtensions: { [key: string]: boolean | undefined } | undefined; const cachedWindowsExecutableExtensionsSettingId = 'terminal.integrated.suggest.windowsExecutableExtensions'; let cachedAvailableCommands: Set | undefined; +let cachedAvailableCommandsLabels: Set | undefined; const cachedBuiltinCommands: Map = new Map(); export const availableSpecs: Fig.Spec[] = [ @@ -211,7 +212,7 @@ function createCompletionItem(cursorPosition: number, prefix: string, commandRes async function getCommandsInPath(env: { [key: string]: string | undefined } = process.env): Promise<{ completionResources: Set | undefined; labels: Set | undefined } | undefined> { - const labels: Set = new Set(); + // Create cache key let pathValue: string | undefined; if (isWindows) { const caseSensitivePathKey = Object.keys(env).find(key => key.toLowerCase() === 'path'); @@ -227,37 +228,59 @@ async function getCommandsInPath(env: { [key: string]: string | undefined } = pr // Check cache if (cachedAvailableCommands && cachedAvailableCommandsPath === pathValue) { - return { completionResources: cachedAvailableCommands, labels }; + return { completionResources: cachedAvailableCommands, labels: cachedAvailableCommandsLabels }; } // Extract executables from PATH const paths = pathValue.split(isWindows ? ';' : ':'); const pathSeparator = isWindows ? '\\' : '/'; - const executables = new Set(); + const promises: Promise | undefined>[] = []; + const labels: Set = new Set(); for (const path of paths) { - try { - const dirExists = await fs.stat(path).then(stat => stat.isDirectory()).catch(() => false); - if (!dirExists) { - continue; - } - const fileResource = vscode.Uri.file(path); - const files = await vscode.workspace.fs.readDirectory(fileResource); - for (const [file, fileType] of files) { - const formattedPath = getFriendlyResourcePath(vscode.Uri.joinPath(fileResource, file), pathSeparator); - if (!labels.has(file) && fileType !== vscode.FileType.Unknown && fileType !== vscode.FileType.Directory && await isExecutable(formattedPath, cachedWindowsExecutableExtensions)) { - executables.add({ label: file, detail: formattedPath }); - labels.add(file); - } + promises.push(getFilesInPath(path, pathSeparator, labels)); + } + + // Merge all results + const executables = new Set(); + const resultSets = await Promise.all(promises); + for (const resultSet of resultSets) { + if (resultSet) { + for (const executable of resultSet) { + executables.add(executable); } - } catch (e) { - // Ignore errors for directories that can't be read - continue; } } + + // Return cachedAvailableCommands = executables; + cachedAvailableCommandsLabels = labels; + cachedAvailableCommandsPath = pathValue; return { completionResources: executables, labels }; } +async function getFilesInPath(path: string, pathSeparator: string, labels: Set): Promise | undefined> { + try { + const dirExists = await fs.stat(path).then(stat => stat.isDirectory()).catch(() => false); + if (!dirExists) { + return undefined; + } + const result = new Set(); + const fileResource = vscode.Uri.file(path); + const files = await vscode.workspace.fs.readDirectory(fileResource); + for (const [file, fileType] of files) { + const formattedPath = getFriendlyResourcePath(vscode.Uri.joinPath(fileResource, file), pathSeparator); + if (!labels.has(file) && fileType !== vscode.FileType.Unknown && fileType !== vscode.FileType.Directory && await isExecutable(formattedPath, cachedWindowsExecutableExtensions)) { + result.add({ label: file, detail: formattedPath }); + labels.add(file); + } + } + return result; + } catch (e) { + // Ignore errors for directories that can't be read + return undefined; + } +} + function getPrefix(commandLine: string, cursorPosition: number): string { // Return an empty string if the command line is empty after trimming if (commandLine.trim() === '') { From 7a9f62939d601473c38f0d3d9529663df9597198 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 05:45:21 -0800 Subject: [PATCH 0270/2632] Move path exe logic into own file --- .../src/env/pathExecutables.ts | 101 ++++++++++++++++ .../terminal-suggest/src/helpers/uri.ts | 20 ++++ .../src/terminalSuggestMain.ts | 111 +----------------- 3 files changed, 127 insertions(+), 105 deletions(-) create mode 100644 extensions/terminal-suggest/src/env/pathExecutables.ts create mode 100644 extensions/terminal-suggest/src/helpers/uri.ts diff --git a/extensions/terminal-suggest/src/env/pathExecutables.ts b/extensions/terminal-suggest/src/env/pathExecutables.ts new file mode 100644 index 000000000000..98b62d345ea9 --- /dev/null +++ b/extensions/terminal-suggest/src/env/pathExecutables.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs/promises'; +import * as vscode from 'vscode'; +import { isExecutable } from '../helpers/executable'; +import { osIsWindows } from '../helpers/os'; +import type { ICompletionResource } from '../types'; +import { getFriendlyResourcePath } from '../helpers/uri'; + +const isWindows = osIsWindows(); +let cachedAvailableCommandsPath: string | undefined; +let cachedWindowsExecutableExtensions: { [key: string]: boolean | undefined } | undefined; +const cachedWindowsExecutableExtensionsSettingId = 'terminal.integrated.suggest.windowsExecutableExtensions'; +let cachedAvailableCommands: Set | undefined; +let cachedAvailableCommandsLabels: Set | undefined; + +export function activatePathExecutables(context: vscode.ExtensionContext) { + if (isWindows) { + cachedWindowsExecutableExtensions = vscode.workspace.getConfiguration('terminal.integrated.suggest').get('windowsExecutableExtensions'); + context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(cachedWindowsExecutableExtensionsSettingId)) { + cachedWindowsExecutableExtensions = vscode.workspace.getConfiguration('terminal.integrated.suggest').get('windowsExecutableExtensions'); + cachedAvailableCommands = undefined; + cachedAvailableCommandsPath = undefined; + } + })); + } +} + +export async function getCommandsInPath(env: { [key: string]: string | undefined } = process.env): Promise<{ completionResources: Set | undefined; labels: Set | undefined } | undefined> { + // Create cache key + let pathValue: string | undefined; + if (isWindows) { + const caseSensitivePathKey = Object.keys(env).find(key => key.toLowerCase() === 'path'); + if (caseSensitivePathKey) { + pathValue = env[caseSensitivePathKey]; + } + } else { + pathValue = env.PATH; + } + if (pathValue === undefined) { + return; + } + + // Check cache + if (cachedAvailableCommands && cachedAvailableCommandsPath === pathValue) { + return { completionResources: cachedAvailableCommands, labels: cachedAvailableCommandsLabels }; + } + + // Extract executables from PATH + const paths = pathValue.split(isWindows ? ';' : ':'); + const pathSeparator = isWindows ? '\\' : '/'; + const promises: Promise | undefined>[] = []; + const labels: Set = new Set(); + for (const path of paths) { + promises.push(getFilesInPath(path, pathSeparator, labels)); + } + + // Merge all results + const executables = new Set(); + const resultSets = await Promise.all(promises); + for (const resultSet of resultSets) { + if (resultSet) { + for (const executable of resultSet) { + executables.add(executable); + } + } + } + + // Return + cachedAvailableCommands = executables; + cachedAvailableCommandsLabels = labels; + cachedAvailableCommandsPath = pathValue; + return { completionResources: executables, labels }; +} + +async function getFilesInPath(path: string, pathSeparator: string, labels: Set): Promise | undefined> { + try { + const dirExists = await fs.stat(path).then(stat => stat.isDirectory()).catch(() => false); + if (!dirExists) { + return undefined; + } + const result = new Set(); + const fileResource = vscode.Uri.file(path); + const files = await vscode.workspace.fs.readDirectory(fileResource); + for (const [file, fileType] of files) { + const formattedPath = getFriendlyResourcePath(vscode.Uri.joinPath(fileResource, file), pathSeparator); + if (!labels.has(file) && fileType !== vscode.FileType.Unknown && fileType !== vscode.FileType.Directory && await isExecutable(formattedPath, cachedWindowsExecutableExtensions)) { + result.add({ label: file, detail: formattedPath }); + labels.add(file); + } + } + return result; + } catch (e) { + // Ignore errors for directories that can't be read + return undefined; + } +} diff --git a/extensions/terminal-suggest/src/helpers/uri.ts b/extensions/terminal-suggest/src/helpers/uri.ts new file mode 100644 index 000000000000..90e09dbc1c29 --- /dev/null +++ b/extensions/terminal-suggest/src/helpers/uri.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +export function getFriendlyResourcePath(uri: vscode.Uri, pathSeparator: string, kind?: vscode.TerminalCompletionItemKind): string { + let path = uri.fsPath; + // Ensure drive is capitalized on Windows + if (pathSeparator === '\\' && path.match(/^[a-zA-Z]:\\/)) { + path = `${path[0].toUpperCase()}:${path.slice(2)}`; + } + if (kind === vscode.TerminalCompletionItemKind.Folder) { + if (!path.endsWith(pathSeparator)) { + path += pathSeparator; + } + } + return path; +} diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 8d278d1c002d..e3f85e5d1326 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -11,13 +11,14 @@ import codeCompletionSpec from './completions/code'; import cdSpec from './completions/cd'; import codeInsidersCompletionSpec from './completions/code-insiders'; import { osIsWindows } from './helpers/os'; -import { isExecutable } from './helpers/executable'; import type { ICompletionResource } from './types'; import { getBashGlobals } from './shell/bash'; import { getZshGlobals } from './shell/zsh'; import { getFishGlobals } from './shell/fish'; import { getPwshGlobals } from './shell/pwsh'; import { getTokenType, TokenType } from './tokens'; +import { activatePathExecutables, getCommandsInPath } from './env/pathExecutables'; +import { getFriendlyResourcePath } from './helpers/uri'; // TODO: remove once API is finalized export const enum TerminalShellType { @@ -37,12 +38,7 @@ export const enum TerminalShellType { } const isWindows = osIsWindows(); -let cachedAvailableCommandsPath: string | undefined; -let cachedWindowsExecutableExtensions: { [key: string]: boolean | undefined } | undefined; -const cachedWindowsExecutableExtensionsSettingId = 'terminal.integrated.suggest.windowsExecutableExtensions'; -let cachedAvailableCommands: Set | undefined; -let cachedAvailableCommandsLabels: Set | undefined; -const cachedBuiltinCommands: Map = new Map(); +const cachedGlobals: Map = new Map(); export const availableSpecs: Fig.Spec[] = [ cdSpec, @@ -63,7 +59,7 @@ const getShellSpecificGlobals: Map): Promise { try { - const cachedCommands = cachedBuiltinCommands.get(shellType); + const cachedCommands = cachedGlobals.get(shellType); if (cachedCommands) { return cachedCommands; } @@ -74,7 +70,7 @@ async function getShellGlobals(shellType: TerminalShellType, existingCommands?: const options: ExecOptionsWithStringEncoding = { encoding: 'utf-8', shell }; const mixedCommands: (string | ICompletionResource)[] | undefined = await getShellSpecificGlobals.get(shellType)?.(options, existingCommands); const normalizedCommands = mixedCommands?.map(command => typeof command === 'string' ? ({ label: command }) : command); - cachedBuiltinCommands.set(shellType, normalizedCommands); + cachedGlobals.set(shellType, normalizedCommands); return normalizedCommands; } catch (error) { @@ -121,17 +117,7 @@ export async function activate(context: vscode.ExtensionContext) { return result.items; } }, '/', '\\')); - - if (isWindows) { - cachedWindowsExecutableExtensions = vscode.workspace.getConfiguration('terminal.integrated.suggest').get('windowsExecutableExtensions'); - context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(cachedWindowsExecutableExtensionsSettingId)) { - cachedWindowsExecutableExtensions = vscode.workspace.getConfiguration('terminal.integrated.suggest').get('windowsExecutableExtensions'); - cachedAvailableCommands = undefined; - cachedAvailableCommandsPath = undefined; - } - })); - } + activatePathExecutables(context); } /** @@ -210,77 +196,6 @@ function createCompletionItem(cursorPosition: number, prefix: string, commandRes }; } - -async function getCommandsInPath(env: { [key: string]: string | undefined } = process.env): Promise<{ completionResources: Set | undefined; labels: Set | undefined } | undefined> { - // Create cache key - let pathValue: string | undefined; - if (isWindows) { - const caseSensitivePathKey = Object.keys(env).find(key => key.toLowerCase() === 'path'); - if (caseSensitivePathKey) { - pathValue = env[caseSensitivePathKey]; - } - } else { - pathValue = env.PATH; - } - if (pathValue === undefined) { - return; - } - - // Check cache - if (cachedAvailableCommands && cachedAvailableCommandsPath === pathValue) { - return { completionResources: cachedAvailableCommands, labels: cachedAvailableCommandsLabels }; - } - - // Extract executables from PATH - const paths = pathValue.split(isWindows ? ';' : ':'); - const pathSeparator = isWindows ? '\\' : '/'; - const promises: Promise | undefined>[] = []; - const labels: Set = new Set(); - for (const path of paths) { - promises.push(getFilesInPath(path, pathSeparator, labels)); - } - - // Merge all results - const executables = new Set(); - const resultSets = await Promise.all(promises); - for (const resultSet of resultSets) { - if (resultSet) { - for (const executable of resultSet) { - executables.add(executable); - } - } - } - - // Return - cachedAvailableCommands = executables; - cachedAvailableCommandsLabels = labels; - cachedAvailableCommandsPath = pathValue; - return { completionResources: executables, labels }; -} - -async function getFilesInPath(path: string, pathSeparator: string, labels: Set): Promise | undefined> { - try { - const dirExists = await fs.stat(path).then(stat => stat.isDirectory()).catch(() => false); - if (!dirExists) { - return undefined; - } - const result = new Set(); - const fileResource = vscode.Uri.file(path); - const files = await vscode.workspace.fs.readDirectory(fileResource); - for (const [file, fileType] of files) { - const formattedPath = getFriendlyResourcePath(vscode.Uri.joinPath(fileResource, file), pathSeparator); - if (!labels.has(file) && fileType !== vscode.FileType.Unknown && fileType !== vscode.FileType.Directory && await isExecutable(formattedPath, cachedWindowsExecutableExtensions)) { - result.add({ label: file, detail: formattedPath }); - labels.add(file); - } - } - return result; - } catch (e) { - // Ignore errors for directories that can't be read - return undefined; - } -} - function getPrefix(commandLine: string, cursorPosition: number): string { // Return an empty string if the command line is empty after trimming if (commandLine.trim() === '') { @@ -519,20 +434,6 @@ function getCompletionItemsFromArgs(args: Fig.SingleOrArray | undefined return { items, filesRequested, foldersRequested }; } -function getFriendlyResourcePath(uri: vscode.Uri, pathSeparator: string, kind?: vscode.TerminalCompletionItemKind): string { - let path = uri.fsPath; - // Ensure drive is capitalized on Windows - if (pathSeparator === '\\' && path.match(/^[a-zA-Z]:\\/)) { - path = `${path[0].toUpperCase()}:${path.slice(2)}`; - } - if (kind === vscode.TerminalCompletionItemKind.Folder) { - if (!path.endsWith(pathSeparator)) { - path += pathSeparator; - } - } - return path; -} - function getShell(shellType: TerminalShellType): string | undefined { switch (shellType) { case TerminalShellType.Bash: From 9131925b74191d5883f991483924ffc1daac5dd6 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 05:56:14 -0800 Subject: [PATCH 0271/2632] Move path exe logic into class --- extensions/terminal-suggest/src/constants.ts | 7 ++ .../src/env/pathExecutableCache.ts | 112 ++++++++++++++++++ .../src/env/pathExecutables.ts | 101 ---------------- .../src/terminalSuggestMain.ts | 9 +- 4 files changed, 125 insertions(+), 104 deletions(-) create mode 100644 extensions/terminal-suggest/src/env/pathExecutableCache.ts delete mode 100644 extensions/terminal-suggest/src/env/pathExecutables.ts diff --git a/extensions/terminal-suggest/src/constants.ts b/extensions/terminal-suggest/src/constants.ts index 37d08189da65..f4ebb9e055d5 100644 --- a/extensions/terminal-suggest/src/constants.ts +++ b/extensions/terminal-suggest/src/constants.ts @@ -11,3 +11,10 @@ export const upstreamSpecs = [ 'rmdir', 'touch', ]; + + +export const enum SettingsIds { + SuggestPrefix = 'terminal.integrated.suggest', + CachedWindowsExecutableExtensions = 'terminal.integrated.suggest.windowsExecutableExtensions', + CachedWindowsExecutableExtensionsSuffixOnly = 'windowsExecutableExtensions', +} diff --git a/extensions/terminal-suggest/src/env/pathExecutableCache.ts b/extensions/terminal-suggest/src/env/pathExecutableCache.ts new file mode 100644 index 000000000000..07ad75b1d5bc --- /dev/null +++ b/extensions/terminal-suggest/src/env/pathExecutableCache.ts @@ -0,0 +1,112 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs/promises'; +import * as vscode from 'vscode'; +import { isExecutable } from '../helpers/executable'; +import { osIsWindows } from '../helpers/os'; +import type { ICompletionResource } from '../types'; +import { getFriendlyResourcePath } from '../helpers/uri'; +import { SettingsIds } from '../constants'; + +const isWindows = osIsWindows(); + +export class PathExecutableCache implements vscode.Disposable { + private _disposables: vscode.Disposable[] = []; + + private _cachedAvailableCommandsPath: string | undefined; + private _cachedWindowsExecutableExtensions: { [key: string]: boolean | undefined } | undefined; + private _cachedAvailableCommands: Set | undefined; + private _cachedAvailableCommandsLabels: Set | undefined; + + constructor() { + if (isWindows) { + this._cachedWindowsExecutableExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly); + this._disposables.push(vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(SettingsIds.CachedWindowsExecutableExtensions)) { + this._cachedWindowsExecutableExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly); + this._cachedAvailableCommands = undefined; + this._cachedAvailableCommandsPath = undefined; + } + })); + } + } + + dispose() { + for (const d of this._disposables) { + d.dispose(); + } + } + + async getCommandsInPath(env: { [key: string]: string | undefined } = process.env): Promise<{ completionResources: Set | undefined; labels: Set | undefined } | undefined> { + // Create cache key + let pathValue: string | undefined; + if (isWindows) { + const caseSensitivePathKey = Object.keys(env).find(key => key.toLowerCase() === 'path'); + if (caseSensitivePathKey) { + pathValue = env[caseSensitivePathKey]; + } + } else { + pathValue = env.PATH; + } + if (pathValue === undefined) { + return; + } + + // Check cache + if (this._cachedAvailableCommands && this._cachedAvailableCommandsPath === pathValue) { + return { completionResources: this._cachedAvailableCommands, labels: this._cachedAvailableCommandsLabels }; + } + + // Extract executables from PATH + const paths = pathValue.split(isWindows ? ';' : ':'); + const pathSeparator = isWindows ? '\\' : '/'; + const promises: Promise | undefined>[] = []; + const labels: Set = new Set(); + for (const path of paths) { + promises.push(this._getFilesInPath(path, pathSeparator, labels)); + } + + // Merge all results + const executables = new Set(); + const resultSets = await Promise.all(promises); + for (const resultSet of resultSets) { + if (resultSet) { + for (const executable of resultSet) { + executables.add(executable); + } + } + } + + // Return + this._cachedAvailableCommands = executables; + this._cachedAvailableCommandsLabels = labels; + this._cachedAvailableCommandsPath = pathValue; + return { completionResources: executables, labels }; + } + + private async _getFilesInPath(path: string, pathSeparator: string, labels: Set): Promise | undefined> { + try { + const dirExists = await fs.stat(path).then(stat => stat.isDirectory()).catch(() => false); + if (!dirExists) { + return undefined; + } + const result = new Set(); + const fileResource = vscode.Uri.file(path); + const files = await vscode.workspace.fs.readDirectory(fileResource); + for (const [file, fileType] of files) { + const formattedPath = getFriendlyResourcePath(vscode.Uri.joinPath(fileResource, file), pathSeparator); + if (!labels.has(file) && fileType !== vscode.FileType.Unknown && fileType !== vscode.FileType.Directory && await isExecutable(formattedPath, this._cachedWindowsExecutableExtensions)) { + result.add({ label: file, detail: formattedPath }); + labels.add(file); + } + } + return result; + } catch (e) { + // Ignore errors for directories that can't be read + return undefined; + } + } +} diff --git a/extensions/terminal-suggest/src/env/pathExecutables.ts b/extensions/terminal-suggest/src/env/pathExecutables.ts deleted file mode 100644 index 98b62d345ea9..000000000000 --- a/extensions/terminal-suggest/src/env/pathExecutables.ts +++ /dev/null @@ -1,101 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as fs from 'fs/promises'; -import * as vscode from 'vscode'; -import { isExecutable } from '../helpers/executable'; -import { osIsWindows } from '../helpers/os'; -import type { ICompletionResource } from '../types'; -import { getFriendlyResourcePath } from '../helpers/uri'; - -const isWindows = osIsWindows(); -let cachedAvailableCommandsPath: string | undefined; -let cachedWindowsExecutableExtensions: { [key: string]: boolean | undefined } | undefined; -const cachedWindowsExecutableExtensionsSettingId = 'terminal.integrated.suggest.windowsExecutableExtensions'; -let cachedAvailableCommands: Set | undefined; -let cachedAvailableCommandsLabels: Set | undefined; - -export function activatePathExecutables(context: vscode.ExtensionContext) { - if (isWindows) { - cachedWindowsExecutableExtensions = vscode.workspace.getConfiguration('terminal.integrated.suggest').get('windowsExecutableExtensions'); - context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(cachedWindowsExecutableExtensionsSettingId)) { - cachedWindowsExecutableExtensions = vscode.workspace.getConfiguration('terminal.integrated.suggest').get('windowsExecutableExtensions'); - cachedAvailableCommands = undefined; - cachedAvailableCommandsPath = undefined; - } - })); - } -} - -export async function getCommandsInPath(env: { [key: string]: string | undefined } = process.env): Promise<{ completionResources: Set | undefined; labels: Set | undefined } | undefined> { - // Create cache key - let pathValue: string | undefined; - if (isWindows) { - const caseSensitivePathKey = Object.keys(env).find(key => key.toLowerCase() === 'path'); - if (caseSensitivePathKey) { - pathValue = env[caseSensitivePathKey]; - } - } else { - pathValue = env.PATH; - } - if (pathValue === undefined) { - return; - } - - // Check cache - if (cachedAvailableCommands && cachedAvailableCommandsPath === pathValue) { - return { completionResources: cachedAvailableCommands, labels: cachedAvailableCommandsLabels }; - } - - // Extract executables from PATH - const paths = pathValue.split(isWindows ? ';' : ':'); - const pathSeparator = isWindows ? '\\' : '/'; - const promises: Promise | undefined>[] = []; - const labels: Set = new Set(); - for (const path of paths) { - promises.push(getFilesInPath(path, pathSeparator, labels)); - } - - // Merge all results - const executables = new Set(); - const resultSets = await Promise.all(promises); - for (const resultSet of resultSets) { - if (resultSet) { - for (const executable of resultSet) { - executables.add(executable); - } - } - } - - // Return - cachedAvailableCommands = executables; - cachedAvailableCommandsLabels = labels; - cachedAvailableCommandsPath = pathValue; - return { completionResources: executables, labels }; -} - -async function getFilesInPath(path: string, pathSeparator: string, labels: Set): Promise | undefined> { - try { - const dirExists = await fs.stat(path).then(stat => stat.isDirectory()).catch(() => false); - if (!dirExists) { - return undefined; - } - const result = new Set(); - const fileResource = vscode.Uri.file(path); - const files = await vscode.workspace.fs.readDirectory(fileResource); - for (const [file, fileType] of files) { - const formattedPath = getFriendlyResourcePath(vscode.Uri.joinPath(fileResource, file), pathSeparator); - if (!labels.has(file) && fileType !== vscode.FileType.Unknown && fileType !== vscode.FileType.Directory && await isExecutable(formattedPath, cachedWindowsExecutableExtensions)) { - result.add({ label: file, detail: formattedPath }); - labels.add(file); - } - } - return result; - } catch (e) { - // Ignore errors for directories that can't be read - return undefined; - } -} diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index e3f85e5d1326..808e26a9c6b7 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -17,7 +17,7 @@ import { getZshGlobals } from './shell/zsh'; import { getFishGlobals } from './shell/fish'; import { getPwshGlobals } from './shell/pwsh'; import { getTokenType, TokenType } from './tokens'; -import { activatePathExecutables, getCommandsInPath } from './env/pathExecutables'; +import { PathExecutableCache } from './env/pathExecutableCache'; import { getFriendlyResourcePath } from './helpers/uri'; // TODO: remove once API is finalized @@ -39,6 +39,7 @@ export const enum TerminalShellType { const isWindows = osIsWindows(); const cachedGlobals: Map = new Map(); +let pathExecutableCache: PathExecutableCache; export const availableSpecs: Fig.Spec[] = [ cdSpec, @@ -80,6 +81,9 @@ async function getShellGlobals(shellType: TerminalShellType, existingCommands?: } export async function activate(context: vscode.ExtensionContext) { + pathExecutableCache = new PathExecutableCache(); + context.subscriptions.push(pathExecutableCache); + context.subscriptions.push(vscode.window.registerTerminalCompletionProvider({ id: 'terminal-suggest', async provideTerminalCompletions(terminal: vscode.Terminal, terminalContext: { commandLine: string; cursorPosition: number }, token: vscode.CancellationToken): Promise { @@ -92,7 +96,7 @@ export async function activate(context: vscode.ExtensionContext) { return; } - const commandsInPath = await getCommandsInPath(terminal.shellIntegration?.env); + const commandsInPath = await pathExecutableCache.getCommandsInPath(terminal.shellIntegration?.env); const shellGlobals = await getShellGlobals(shellType, commandsInPath?.labels) ?? []; if (!commandsInPath?.completionResources) { return; @@ -117,7 +121,6 @@ export async function activate(context: vscode.ExtensionContext) { return result.items; } }, '/', '\\')); - activatePathExecutables(context); } /** From af8504c3ac7f895e1b085e3bdbb9946f0be5cd33 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 06:04:08 -0800 Subject: [PATCH 0272/2632] Add test to prevent future simple cache breakages --- .../src/env/pathExecutableCache.ts | 15 +++++------ .../src/test/env/pathExecutableCache.test.ts | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts diff --git a/extensions/terminal-suggest/src/env/pathExecutableCache.ts b/extensions/terminal-suggest/src/env/pathExecutableCache.ts index 07ad75b1d5bc..15f1bbcb694e 100644 --- a/extensions/terminal-suggest/src/env/pathExecutableCache.ts +++ b/extensions/terminal-suggest/src/env/pathExecutableCache.ts @@ -18,8 +18,7 @@ export class PathExecutableCache implements vscode.Disposable { private _cachedAvailableCommandsPath: string | undefined; private _cachedWindowsExecutableExtensions: { [key: string]: boolean | undefined } | undefined; - private _cachedAvailableCommands: Set | undefined; - private _cachedAvailableCommandsLabels: Set | undefined; + private _cachedCommandsInPath: { completionResources: Set | undefined; labels: Set | undefined } | undefined; constructor() { if (isWindows) { @@ -27,8 +26,7 @@ export class PathExecutableCache implements vscode.Disposable { this._disposables.push(vscode.workspace.onDidChangeConfiguration(e => { if (e.affectsConfiguration(SettingsIds.CachedWindowsExecutableExtensions)) { this._cachedWindowsExecutableExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly); - this._cachedAvailableCommands = undefined; - this._cachedAvailableCommandsPath = undefined; + this._cachedCommandsInPath = undefined; } })); } @@ -56,8 +54,8 @@ export class PathExecutableCache implements vscode.Disposable { } // Check cache - if (this._cachedAvailableCommands && this._cachedAvailableCommandsPath === pathValue) { - return { completionResources: this._cachedAvailableCommands, labels: this._cachedAvailableCommandsLabels }; + if (this._cachedCommandsInPath && this._cachedAvailableCommandsPath === pathValue) { + return this._cachedCommandsInPath; } // Extract executables from PATH @@ -81,10 +79,9 @@ export class PathExecutableCache implements vscode.Disposable { } // Return - this._cachedAvailableCommands = executables; - this._cachedAvailableCommandsLabels = labels; this._cachedAvailableCommandsPath = pathValue; - return { completionResources: executables, labels }; + this._cachedCommandsInPath = { completionResources: executables, labels }; + return this._cachedCommandsInPath; } private async _getFilesInPath(path: string, pathSeparator: string, labels: Set): Promise | undefined> { diff --git a/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts b/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts new file mode 100644 index 000000000000..4ba5451eafc2 --- /dev/null +++ b/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import { strictEqual } from 'node:assert'; +import { PathExecutableCache } from '../../env/pathExecutableCache'; + +suite('PathExecutableCache', () => { + test('cache should return empty for empty PATH', async () => { + const cache = new PathExecutableCache(); + const result = await cache.getCommandsInPath({ PATH: '' }); + strictEqual(Array.from(result!.completionResources!).length, 0); + strictEqual(Array.from(result!.labels!).length, 0); + }); + + test('caching is working on successive calls', async () => { + const cache = new PathExecutableCache(); + const env = { PATH: process.env.PATH }; + const result = await cache.getCommandsInPath(env); + const result2 = await cache.getCommandsInPath(env); + strictEqual(result, result2); + }); +}); From 276c0edf9157b9bd08c925a07eb6e31da13f0b43 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 06:14:23 -0800 Subject: [PATCH 0273/2632] Only invalidate terminal suggestions when whitespace is encountered Fixes #239017 --- .../suggest/browser/terminalSuggestAddon.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index 1af621de8cf1..9c70271e2ad4 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -322,12 +322,15 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest return; } - // Hide the widget if the cursor moves to the left of the initial position as the - // completions are no longer valid - // to do: get replacement length to be correct, readd this? - if (this._currentPromptInputState && this._currentPromptInputState.cursorIndex <= this._leadingLineContent.length) { - this.hideSuggestWidget(); - return; + // Hide the widget if the cursor moves to the left and invalidates the completions. + // Originally this was to the left of the initial position that the completions were + // requested, but since extensions are expected to allow the client-side to filter, they are + // only invalidated when whitespace is encountered. + if (this._currentPromptInputState && this._currentPromptInputState.cursorIndex < this._leadingLineContent.length) { + if (this._currentPromptInputState.cursorIndex === 0 || this._leadingLineContent[this._currentPromptInputState.cursorIndex - 1].match(/\s/)) { + this.hideSuggestWidget(); + return; + } } if (this._terminalSuggestWidgetVisibleContextKey.get()) { From 863147b23b72c02d5993ae421f2a0ba726f952d7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 06:49:16 -0800 Subject: [PATCH 0274/2632] Penalize files/dirs starting with _, otherwise ignore punctuation Fixes #238085 --- .../suggest/browser/simpleCompletionItem.ts | 4 ++++ .../suggest/browser/simpleCompletionModel.ts | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts b/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts index a9b69068baab..5bfb52723431 100644 --- a/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts +++ b/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts @@ -5,6 +5,7 @@ import { FuzzyScore } from '../../../../base/common/filters.js'; import { MarkdownString } from '../../../../base/common/htmlContent.js'; +import { basename } from '../../../../base/common/path.js'; import { isWindows } from '../../../../base/common/platform.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; @@ -73,6 +74,8 @@ export class SimpleCompletionItem { */ readonly labelLowNormalizedPath: string; + readonly unscorePenalty: number = 0; + /** * The file extension part from {@link labelLow}. */ @@ -109,6 +112,7 @@ export class SimpleCompletionItem { if (completion.isDirectory) { this.labelLowNormalizedPath = this.labelLowNormalizedPath.replace(/\/$/, ''); } + this.unscorePenalty = basename(this.labelLowNormalizedPath).startsWith('_') ? 1 : 0; } } } diff --git a/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts b/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts index 7cc99b0936e1..4e59fc802e89 100644 --- a/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts +++ b/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts @@ -188,11 +188,13 @@ export class SimpleCompletionModel { return score; } } + // Sort by the score score = b.score[0] - a.score[0]; if (score !== 0) { return score; } + // Sort files with the same score against each other specially const isArg = leadingLineContent.includes(' '); if (!isArg && a.fileExtLow.length > 0 && b.fileExtLow.length > 0) { @@ -212,6 +214,13 @@ export class SimpleCompletionModel { return score; } } + + // Sort by unscore penalty (eg. `__init__/` should be penalized) + if (a.unscorePenalty !== b.unscorePenalty) { + return a.unscorePenalty - b.unscorePenalty; + } + + // Sort by folder depth (eg. `vscode/` should come before `vscode-.../`) if (a.labelLowNormalizedPath && b.labelLowNormalizedPath) { // Directories // Count depth of path (number of / or \ occurrences) @@ -228,8 +237,10 @@ export class SimpleCompletionModel { return 1; // `b` is a prefix of `a`, so `b` should come first } } - // Sort alphabetically - return a.labelLow.localeCompare(b.labelLow); + + // Sort alphabetically, ignoring punctuation causes dot files to be mixed in rather than + // all at the top + return a.labelLow.localeCompare(b.labelLow, undefined, { ignorePunctuation: true }); }); this._refilterKind = Refilter.Nothing; From 7fe56a59b5ebcc231bdc3cce4efa60a9ccf85bf3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 07:41:41 -0800 Subject: [PATCH 0275/2632] Support CDPATH in terminal suggest Fixes #239401 --- .../browser/terminalCompletionService.ts | 47 +++++++++++++++---- .../suggest/browser/terminalSuggestAddon.ts | 2 +- .../common/terminalSuggestConfiguration.ts | 13 +++++ 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index 36d4de75fcd4..18ecd71882b7 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -12,6 +12,7 @@ import { URI } from '../../../../../base/common/uri.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js'; +import { TerminalCapability, type ITerminalCapabilityStore } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; import { GeneralShellType, TerminalShellType } from '../../../../../platform/terminal/common/terminal.js'; import { ISimpleCompletion } from '../../../../services/suggest/browser/simpleCompletionItem.js'; import { TerminalSuggestSettingId } from '../common/terminalSuggestConfiguration.js'; @@ -82,7 +83,7 @@ export interface ITerminalCompletionService { _serviceBrand: undefined; readonly providers: IterableIterator; registerTerminalCompletionProvider(extensionIdentifier: string, id: string, provider: ITerminalCompletionProvider, ...triggerCharacters: string[]): IDisposable; - provideCompletions(promptValue: string, cursorPosition: number, shellType: TerminalShellType, token: CancellationToken, triggerCharacter?: boolean, skipExtensionCompletions?: boolean): Promise; + provideCompletions(promptValue: string, cursorPosition: number, shellType: TerminalShellType, capabilities: ITerminalCapabilityStore, token: CancellationToken, triggerCharacter?: boolean, skipExtensionCompletions?: boolean): Promise; } export class TerminalCompletionService extends Disposable implements ITerminalCompletionService { @@ -101,7 +102,8 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo } } - constructor(@IConfigurationService private readonly _configurationService: IConfigurationService, + constructor( + @IConfigurationService private readonly _configurationService: IConfigurationService, @IFileService private readonly _fileService: IFileService ) { super(); @@ -127,7 +129,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo }); } - async provideCompletions(promptValue: string, cursorPosition: number, shellType: TerminalShellType, token: CancellationToken, triggerCharacter?: boolean, skipExtensionCompletions?: boolean): Promise { + async provideCompletions(promptValue: string, cursorPosition: number, shellType: TerminalShellType, capabilities: ITerminalCapabilityStore, token: CancellationToken, triggerCharacter?: boolean, skipExtensionCompletions?: boolean): Promise { if (!this._providers || !this._providers.values || cursorPosition < 0) { return undefined; } @@ -153,7 +155,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo if (skipExtensionCompletions) { providers = providers.filter(p => p.isBuiltin); - return this._collectCompletions(providers, shellType, promptValue, cursorPosition, token); + return this._collectCompletions(providers, shellType, promptValue, cursorPosition, capabilities, token); } const providerConfig: { [key: string]: boolean } = this._configurationService.getValue(TerminalSuggestSettingId.Providers); @@ -166,10 +168,10 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo return; } - return this._collectCompletions(providers, shellType, promptValue, cursorPosition, token); + return this._collectCompletions(providers, shellType, promptValue, cursorPosition, capabilities, token); } - private async _collectCompletions(providers: ITerminalCompletionProvider[], shellType: TerminalShellType, promptValue: string, cursorPosition: number, token: CancellationToken): Promise { + private async _collectCompletions(providers: ITerminalCompletionProvider[], shellType: TerminalShellType, promptValue: string, cursorPosition: number, capabilities: ITerminalCapabilityStore, token: CancellationToken): Promise { const completionPromises = providers.map(async provider => { if (provider.shellTypes && !provider.shellTypes.includes(shellType)) { return undefined; @@ -193,7 +195,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo return completionItems; } if (completions.resourceRequestConfig) { - const resourceCompletions = await this.resolveResources(completions.resourceRequestConfig, promptValue, cursorPosition, provider.id); + const resourceCompletions = await this.resolveResources(completions.resourceRequestConfig, promptValue, cursorPosition, provider.id, capabilities); if (resourceCompletions) { completionItems.push(...resourceCompletions); } @@ -206,7 +208,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo return results.filter(result => !!result).flat(); } - async resolveResources(resourceRequestConfig: TerminalResourceRequestConfig, promptValue: string, cursorPosition: number, provider: string): Promise { + async resolveResources(resourceRequestConfig: TerminalResourceRequestConfig, promptValue: string, cursorPosition: number, provider: string, capabilities: ITerminalCapabilityStore): Promise { if (resourceRequestConfig.shouldNormalizePrefix) { // for tests, make sure the right path separator is used promptValue = promptValue.replaceAll(/[\\/]/g, resourceRequestConfig.pathSeparator); @@ -349,6 +351,35 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo } } + // Support $CDPATH specially for the `cd` command only + if (promptValue.startsWith('cd ')) { + const config = this._configurationService.getValue(TerminalSuggestSettingId.CdPath); + if (config === 'absolute' || config === 'relative') { + const cdPath = capabilities.get(TerminalCapability.ShellEnvDetection)?.env?.get('CDPATH'); + if (cdPath) { + const cdPathEntries = cdPath.split(useForwardSlash ? ';' : ':'); + for (const cdPathEntry of cdPathEntries) { + const fileStat = await this._fileService.resolve(URI.file(cdPathEntry), { resolveSingleChildDescendants: true }); + if (fileStat?.children) { + for (const child of fileStat.children) { + const label = config === 'relative' ? basename(child.resource.fsPath) : getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator); + resourceCompletions.push({ + label, + provider, + kind: TerminalCompletionItemKind.Folder, + isDirectory: child.isDirectory, + isFile: child.isFile, + detail: `CDPATH`, + replacementIndex: cursorPosition - lastWord.length, + replacementLength: lastWord.length + }); + } + } + } + } + } + } + // Add parent directory to the bottom of the list because it's not as useful as other suggestions // // For example: diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index 1af621de8cf1..599199ad9e87 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -169,7 +169,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest }; this._requestedCompletionsIndex = this._currentPromptInputState.cursorIndex; - const providedCompletions = await this._terminalCompletionService.provideCompletions(this._currentPromptInputState.prefix, this._currentPromptInputState.cursorIndex, this.shellType, token, doNotRequestExtensionCompletions); + const providedCompletions = await this._terminalCompletionService.provideCompletions(this._currentPromptInputState.prefix, this._currentPromptInputState.cursorIndex, this.shellType, this._capabilities, token, doNotRequestExtensionCompletions); if (!providedCompletions?.length || token.isCancellationRequested) { return; diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts index e11d8d0587c2..664892c681f1 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/common/terminalSuggestConfiguration.ts @@ -17,6 +17,7 @@ export const enum TerminalSuggestSettingId { WindowsExecutableExtensions = 'terminal.integrated.suggest.windowsExecutableExtensions', Providers = 'terminal.integrated.suggest.providers', ShowStatusBar = 'terminal.integrated.suggest.showStatusBar', + CdPath = 'terminal.integrated.suggest.cdPath', } export const windowsDefaultExecutableExtensions: string[] = [ @@ -134,6 +135,18 @@ export const terminalSuggestConfiguration: IStringDictionary Date: Sat, 1 Feb 2025 07:46:25 -0800 Subject: [PATCH 0276/2632] Handle errors and improve detail when config is absolute --- .../browser/terminalCompletionService.ts | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index 18ecd71882b7..a3bc6a1e3bc9 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -359,22 +359,26 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo if (cdPath) { const cdPathEntries = cdPath.split(useForwardSlash ? ';' : ':'); for (const cdPathEntry of cdPathEntries) { - const fileStat = await this._fileService.resolve(URI.file(cdPathEntry), { resolveSingleChildDescendants: true }); - if (fileStat?.children) { - for (const child of fileStat.children) { - const label = config === 'relative' ? basename(child.resource.fsPath) : getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator); - resourceCompletions.push({ - label, - provider, - kind: TerminalCompletionItemKind.Folder, - isDirectory: child.isDirectory, - isFile: child.isFile, - detail: `CDPATH`, - replacementIndex: cursorPosition - lastWord.length, - replacementLength: lastWord.length - }); + try { + const fileStat = await this._fileService.resolve(URI.file(cdPathEntry), { resolveSingleChildDescendants: true }); + if (fileStat?.children) { + for (const child of fileStat.children) { + const useRelative = config === 'relative'; + const label = useRelative ? basename(child.resource.fsPath) : getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator); + const detail = useRelative ? `CDPATH ${getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator)}` : `CDPATH`; + resourceCompletions.push({ + label, + provider, + kind: TerminalCompletionItemKind.Folder, + isDirectory: child.isDirectory, + isFile: child.isFile, + detail, + replacementIndex: cursorPosition - lastWord.length, + replacementLength: lastWord.length + }); + } } - } + } catch { /* ignore */ } } } } From 732c306210defed1e0ff2489d260821a84a9546d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 07:49:57 -0800 Subject: [PATCH 0277/2632] Fix compile in completion tests --- .../browser/terminalCompletionService.test.ts | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index 7cdac7c3d674..bdc89e39c17c 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -13,6 +13,7 @@ import { TestInstantiationService } from '../../../../../../platform/instantiati import { createFileStat } from '../../../../../test/common/workbenchTestServices.js'; import { TestConfigurationService } from '../../../../../../platform/configuration/test/common/testConfigurationService.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; +import { TerminalCapabilityStore } from '../../../../../../platform/terminal/common/capabilities/terminalCapabilityStore.js'; const pathSeparator = isWindows ? '\\' : '/'; @@ -77,7 +78,7 @@ suite('TerminalCompletionService', () => { suite('resolveResources should return undefined', () => { test('if cwd is not provided', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { pathSeparator }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider, new TerminalCapabilityStore()); assert(!result); }); @@ -87,7 +88,7 @@ suite('TerminalCompletionService', () => { pathSeparator }; validResources = [URI.parse('file:///test')]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider, new TerminalCapabilityStore()); assert(!result); }); }); @@ -107,7 +108,7 @@ suite('TerminalCompletionService', () => { foldersRequested: true, pathSeparator }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '', 1, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '', 1, provider, new TerminalCapabilityStore()); assertCompletions(result, [ { label: '.', detail: '/test/' }, @@ -123,7 +124,7 @@ suite('TerminalCompletionService', () => { pathSeparator, shouldNormalizePrefix: true }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 3, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 3, provider, new TerminalCapabilityStore()); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -139,7 +140,7 @@ suite('TerminalCompletionService', () => { pathSeparator, shouldNormalizePrefix: true }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ./', 5, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ./', 5, provider, new TerminalCapabilityStore()); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -154,7 +155,7 @@ suite('TerminalCompletionService', () => { pathSeparator, shouldNormalizePrefix: true }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ./f', 6, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ./f', 6, provider, new TerminalCapabilityStore()); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -170,7 +171,7 @@ suite('TerminalCompletionService', () => { shouldNormalizePrefix: true, env: { HOME: '/test/' } }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ~/', 5, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ~/', 5, provider, new TerminalCapabilityStore()); assertCompletions(result, [ { label: '~/', detail: '/test/' }, @@ -197,7 +198,7 @@ suite('TerminalCompletionService', () => { pathSeparator, shouldNormalizePrefix: true }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 2, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 2, provider, new TerminalCapabilityStore()); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -217,7 +218,7 @@ suite('TerminalCompletionService', () => { pathSeparator, shouldNormalizePrefix: true }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './h', 3, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './h', 3, provider, new TerminalCapabilityStore()); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -245,7 +246,7 @@ suite('TerminalCompletionService', () => { }; validResources = [URI.parse('file:///usr')]; childResources = []; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '/usr/', 5, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '/usr/', 5, provider, new TerminalCapabilityStore()); assertCompletions(result, [ { label: '/usr/', detail: '/' }, @@ -267,7 +268,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///C:/test/anotherFolder/'), isDirectory: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '.\\folder', 8, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '.\\folder', 8, provider, new TerminalCapabilityStore()); assertCompletions(result, [ { label: '.\\', detail: 'C:\\test\\' }, @@ -290,7 +291,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///test/foldera/'), isDirectory: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './folder', 8, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './folder', 8, provider, new TerminalCapabilityStore()); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -313,7 +314,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///test/folder1/'), isDirectory: true }, { resource: URI.parse('file:///test/folder2/'), isDirectory: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '', 0, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '', 0, provider, new TerminalCapabilityStore()); assertCompletions(result, [ { label: '.', detail: '/test/' }, @@ -335,7 +336,7 @@ suite('TerminalCompletionService', () => { resource: URI.parse(`file:///test/folder${i}/`), isDirectory: true })); - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 2, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 2, provider, new TerminalCapabilityStore()); assert(result); // includes the 1000 folders + ./ and ./../ @@ -356,7 +357,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///test/folder1/'), isDirectory: true }, { resource: URI.parse('file:///test/folder2/'), isDirectory: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './folder1', 10, provider); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './folder1', 10, provider, new TerminalCapabilityStore()); assertCompletions(result, [ { label: './', detail: '/test/' }, From 74974c9bb3091d3aca8c20999c82e1ba2ccc754f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 08:07:40 -0800 Subject: [PATCH 0278/2632] Add tests for CDPATH feature --- .../browser/terminalCompletionService.ts | 3 + .../browser/terminalCompletionService.test.ts | 89 +++++++++++++++---- 2 files changed, 76 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index a3bc6a1e3bc9..ea42bf6b6f30 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -363,6 +363,9 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo const fileStat = await this._fileService.resolve(URI.file(cdPathEntry), { resolveSingleChildDescendants: true }); if (fileStat?.children) { for (const child of fileStat.children) { + if (!child.isDirectory) { + continue; + } const useRelative = config === 'relative'; const label = useRelative ? basename(child.resource.fsPath) : getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator); const detail = useRelative ? `CDPATH ${getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator)}` : `CDPATH`; diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index bdc89e39c17c..92d723eb8d9a 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -14,6 +14,8 @@ import { createFileStat } from '../../../../../test/common/workbenchTestServices import { TestConfigurationService } from '../../../../../../platform/configuration/test/common/testConfigurationService.js'; import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { TerminalCapabilityStore } from '../../../../../../platform/terminal/common/capabilities/terminalCapabilityStore.js'; +import { ShellEnvDetectionCapability } from '../../../../../../platform/terminal/common/capabilities/shellEnvDetectionCapability.js'; +import { TerminalCapability } from '../../../../../../platform/terminal/common/capabilities/capabilities.js'; const pathSeparator = isWindows ? '\\' : '/'; @@ -50,6 +52,7 @@ suite('TerminalCompletionService', () => { const store = ensureNoDisposablesAreLeakedInTestSuite(); let instantiationService: TestInstantiationService; let configurationService: TestConfigurationService; + let capabilities: TerminalCapabilityStore; let validResources: URI[]; let childResources: { resource: URI; isFile?: boolean; isDirectory?: boolean }[]; let terminalCompletionService: TerminalCompletionService; @@ -67,18 +70,20 @@ suite('TerminalCompletionService', () => { return createFileStat(resource); }, async resolve(resource: URI, options: IResolveMetadataFileOptions): Promise { - return createFileStat(resource, undefined, undefined, undefined, childResources); + const children = childResources.filter(e => e.resource.fsPath.startsWith(resource.fsPath)); + return createFileStat(resource, undefined, undefined, undefined, children); }, }); terminalCompletionService = store.add(instantiationService.createInstance(TerminalCompletionService)); validResources = []; childResources = []; + capabilities = store.add(new TerminalCapabilityStore()); }); suite('resolveResources should return undefined', () => { test('if cwd is not provided', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { pathSeparator }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider, capabilities); assert(!result); }); @@ -88,7 +93,7 @@ suite('TerminalCompletionService', () => { pathSeparator }; validResources = [URI.parse('file:///test')]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider, capabilities); assert(!result); }); }); @@ -108,7 +113,7 @@ suite('TerminalCompletionService', () => { foldersRequested: true, pathSeparator }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '', 1, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '', 1, provider, capabilities); assertCompletions(result, [ { label: '.', detail: '/test/' }, @@ -124,7 +129,7 @@ suite('TerminalCompletionService', () => { pathSeparator, shouldNormalizePrefix: true }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 3, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 3, provider, capabilities); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -140,7 +145,7 @@ suite('TerminalCompletionService', () => { pathSeparator, shouldNormalizePrefix: true }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ./', 5, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ./', 5, provider, capabilities); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -155,7 +160,7 @@ suite('TerminalCompletionService', () => { pathSeparator, shouldNormalizePrefix: true }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ./f', 6, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ./f', 6, provider, capabilities); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -171,7 +176,7 @@ suite('TerminalCompletionService', () => { shouldNormalizePrefix: true, env: { HOME: '/test/' } }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ~/', 5, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ~/', 5, provider, capabilities); assertCompletions(result, [ { label: '~/', detail: '/test/' }, @@ -198,7 +203,7 @@ suite('TerminalCompletionService', () => { pathSeparator, shouldNormalizePrefix: true }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 2, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 2, provider, capabilities); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -218,7 +223,7 @@ suite('TerminalCompletionService', () => { pathSeparator, shouldNormalizePrefix: true }; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './h', 3, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './h', 3, provider, capabilities); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -246,7 +251,7 @@ suite('TerminalCompletionService', () => { }; validResources = [URI.parse('file:///usr')]; childResources = []; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '/usr/', 5, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '/usr/', 5, provider, capabilities); assertCompletions(result, [ { label: '/usr/', detail: '/' }, @@ -268,7 +273,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///C:/test/anotherFolder/'), isDirectory: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '.\\folder', 8, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '.\\folder', 8, provider, capabilities); assertCompletions(result, [ { label: '.\\', detail: 'C:\\test\\' }, @@ -291,7 +296,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///test/foldera/'), isDirectory: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './folder', 8, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './folder', 8, provider, capabilities); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -314,7 +319,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///test/folder1/'), isDirectory: true }, { resource: URI.parse('file:///test/folder2/'), isDirectory: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '', 0, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '', 0, provider, capabilities); assertCompletions(result, [ { label: '.', detail: '/test/' }, @@ -336,7 +341,7 @@ suite('TerminalCompletionService', () => { resource: URI.parse(`file:///test/folder${i}/`), isDirectory: true })); - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 2, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './', 2, provider, capabilities); assert(result); // includes the 1000 folders + ./ and ./../ @@ -357,7 +362,7 @@ suite('TerminalCompletionService', () => { { resource: URI.parse('file:///test/folder1/'), isDirectory: true }, { resource: URI.parse('file:///test/folder2/'), isDirectory: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './folder1', 10, provider, new TerminalCapabilityStore()); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, './folder1', 10, provider, capabilities); assertCompletions(result, [ { label: './', detail: '/test/' }, @@ -367,4 +372,56 @@ suite('TerminalCompletionService', () => { ], { replacementIndex: 1, replacementLength: 9 }); }); }); + + suite('cdpath', () => { + let shellEnvDetection: ShellEnvDetectionCapability; + + setup(() => { + validResources = [URI.parse('file:///test')]; + childResources = [ + { resource: URI.parse('file:///cdpath_value/folder1/'), isDirectory: true }, + { resource: URI.parse('file:///cdpath_value/file1.txt'), isFile: true }, + ]; + + shellEnvDetection = store.add(new ShellEnvDetectionCapability()); + shellEnvDetection.setEnvironment({ CDPATH: '/cdpath_value' }, true); + capabilities.add(TerminalCapability.ShellEnvDetection, shellEnvDetection); + }); + + test('cd | should show paths from $CDPATH (relative)', async () => { + configurationService.setUserConfiguration('terminal.integrated.suggest.cdPath', 'relative'); + const resourceRequestConfig: TerminalResourceRequestConfig = { + cwd: URI.parse('file:///test'), + foldersRequested: true, + filesRequested: true, + pathSeparator, + shouldNormalizePrefix: true + }; + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider, capabilities); + + assertCompletions(result, [ + { label: '.', detail: '/test/' }, + { label: 'folder1', detail: 'CDPATH /cdpath_value/folder1/' }, + { label: '../', detail: '/' }, + ], { replacementIndex: 3, replacementLength: 0 }); + }); + + test('cd | should show paths from $CDPATH (absolute)', async () => { + configurationService.setUserConfiguration('terminal.integrated.suggest.cdPath', 'absolute'); + const resourceRequestConfig: TerminalResourceRequestConfig = { + cwd: URI.parse('file:///test'), + foldersRequested: true, + filesRequested: true, + pathSeparator, + shouldNormalizePrefix: true + }; + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider, capabilities); + + assertCompletions(result, [ + { label: '.', detail: '/test/' }, + { label: '/cdpath_value/folder1/', detail: 'CDPATH' }, + { label: '../', detail: '/' }, + ], { replacementIndex: 3, replacementLength: 0 }); + }); + }); }); From 154cd450456f1820487b4ddd8ac65d14f3bae4dc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 09:03:56 -0800 Subject: [PATCH 0279/2632] Terminal suggest absolute path support Fixes #237979 --- .../browser/terminalCompletionService.ts | 234 +++++++++++------- .../browser/terminalCompletionService.test.ts | 56 ++++- 2 files changed, 199 insertions(+), 91 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index ea42bf6b6f30..4eb91c8ef62b 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -220,11 +220,6 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo return; } - const fileStat = await this._fileService.resolve(cwd, { resolveSingleChildDescendants: true }); - if (!fileStat || !fileStat?.children) { - return; - } - const resourceCompletions: ITerminalCompletion[] = []; const cursorPrefix = promptValue.substring(0, cursorPosition); @@ -271,39 +266,107 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo return resourceCompletions; } } - // Add current directory. This should be shown at the top because it will be an exact match - // and therefore highlight the detail, plus it improves the experience when runOnEnter is - // used. - // - // For example: - // - `|` -> `.`, this does not have the trailing `/` intentionally as it's common to - // complete the current working directory and we do not want to complete `./` when - // `runOnEnter` is used. - // - `./src/|` -> `./src/` - if (foldersRequested) { - resourceCompletions.push({ - label: lastWordFolder.length === 0 ? '.' : lastWordFolder, - provider, - kind: TerminalCompletionItemKind.Folder, - isDirectory: true, - isFile: false, - detail: getFriendlyPath(cwd, resourceRequestConfig.pathSeparator), - replacementIndex: cursorPosition - lastWord.length, - replacementLength: lastWord.length - }); - } // Handle absolute paths differently to avoid adding `./` prefixes // TODO: Deal with git bash case const isAbsolutePath = useForwardSlash - ? /^[a-zA-Z]:\\/.test(lastWord) - : lastWord.startsWith(resourceRequestConfig.pathSeparator) && lastWord.endsWith(resourceRequestConfig.pathSeparator); - - // Add all direct children files or folders - // - // For example: - // - `cd ./src/` -> `cd ./src/folder1`, ... - if (!isAbsolutePath) { + ? lastWord.startsWith(resourceRequestConfig.pathSeparator) && lastWord.endsWith(resourceRequestConfig.pathSeparator) + : /^[a-zA-Z]:[\\\/]/.test(lastWord); + + if (isAbsolutePath) { + + const fileStat = await this._fileService.resolve(URI.file(lastWordFolder), { resolveSingleChildDescendants: true }); + if (!fileStat?.children) { + return; + } + + // Add current directory. This should be shown at the top because it will be an exact match + // and therefore highlight the detail, plus it improves the experience when runOnEnter is + // used. + // + // For example: + // - `c:/foo/|` -> `c:/foo/` + if (foldersRequested) { + resourceCompletions.push({ + label: lastWordFolder.length === 0 ? '.' : lastWordFolder, + provider, + kind: TerminalCompletionItemKind.Folder, + isDirectory: true, + isFile: false, + detail: getFriendlyPath(cwd, resourceRequestConfig.pathSeparator), + replacementIndex: cursorPosition - lastWord.length, + replacementLength: lastWord.length + }); + } + + // Add all direct children files or folders + // + // For example: + // - `cd c:/src/` -> `cd c:/src/folder1/`, ... + for (const child of fileStat.children) { + if ( + (child.isDirectory && !foldersRequested) || + (child.isFile && !filesRequested) + ) { + continue; + } + + let label = lastWordFolder; + if (!label.endsWith(resourceRequestConfig.pathSeparator)) { + label += resourceRequestConfig.pathSeparator; + } + label += child.name; + if (child.isDirectory) { + label += resourceRequestConfig.pathSeparator; + } + + const kind = child.isDirectory ? TerminalCompletionItemKind.Folder : TerminalCompletionItemKind.File; + + resourceCompletions.push({ + label, + provider, + kind, + detail: getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator, kind), + isDirectory: child.isDirectory, + isFile: child.isFile, + replacementIndex: cursorPosition - lastWord.length, + replacementLength: lastWord.length + }); + } + + } else { // !isAbsolutePath + + const fileStat = await this._fileService.resolve(cwd, { resolveSingleChildDescendants: true }); + if (!fileStat?.children) { + return; + } + + // Add current directory. This should be shown at the top because it will be an exact match + // and therefore highlight the detail, plus it improves the experience when runOnEnter is + // used. + // + // For example: + // - `|` -> `.`, this does not have the trailing `/` intentionally as it's common to + // complete the current working directory and we do not want to complete `./` when + // `runOnEnter` is used. + // - `./src/|` -> `./src/` + if (foldersRequested) { + resourceCompletions.push({ + label: lastWordFolder.length === 0 ? '.' : lastWordFolder, + provider, + kind: TerminalCompletionItemKind.Folder, + isDirectory: true, + isFile: false, + detail: getFriendlyPath(cwd, resourceRequestConfig.pathSeparator), + replacementIndex: cursorPosition - lastWord.length, + replacementLength: lastWord.length + }); + } + + // Add all direct children files or folders + // + // For example: + // - `cd ./src/` -> `cd ./src/folder1/`, ... for (const stat of fileStat.children) { let kind: TerminalCompletionItemKind | undefined; if (foldersRequested && stat.isDirectory) { @@ -349,64 +412,65 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo replacementLength: lastWord.length }); } - } - // Support $CDPATH specially for the `cd` command only - if (promptValue.startsWith('cd ')) { - const config = this._configurationService.getValue(TerminalSuggestSettingId.CdPath); - if (config === 'absolute' || config === 'relative') { - const cdPath = capabilities.get(TerminalCapability.ShellEnvDetection)?.env?.get('CDPATH'); - if (cdPath) { - const cdPathEntries = cdPath.split(useForwardSlash ? ';' : ':'); - for (const cdPathEntry of cdPathEntries) { - try { - const fileStat = await this._fileService.resolve(URI.file(cdPathEntry), { resolveSingleChildDescendants: true }); - if (fileStat?.children) { - for (const child of fileStat.children) { - if (!child.isDirectory) { - continue; + // Support $CDPATH specially for the `cd` command only + if (promptValue.startsWith('cd ')) { + const config = this._configurationService.getValue(TerminalSuggestSettingId.CdPath); + if (config === 'absolute' || config === 'relative') { + const cdPath = capabilities.get(TerminalCapability.ShellEnvDetection)?.env?.get('CDPATH'); + if (cdPath) { + const cdPathEntries = cdPath.split(useForwardSlash ? ';' : ':'); + for (const cdPathEntry of cdPathEntries) { + try { + const fileStat = await this._fileService.resolve(URI.file(cdPathEntry), { resolveSingleChildDescendants: true }); + if (fileStat?.children) { + for (const child of fileStat.children) { + if (!child.isDirectory) { + continue; + } + const useRelative = config === 'relative'; + const label = useRelative ? basename(child.resource.fsPath) : getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator); + const detail = useRelative ? `CDPATH ${getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator)}` : `CDPATH`; + resourceCompletions.push({ + label, + provider, + kind: TerminalCompletionItemKind.Folder, + isDirectory: child.isDirectory, + isFile: child.isFile, + detail, + replacementIndex: cursorPosition - lastWord.length, + replacementLength: lastWord.length + }); } - const useRelative = config === 'relative'; - const label = useRelative ? basename(child.resource.fsPath) : getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator); - const detail = useRelative ? `CDPATH ${getFriendlyPath(child.resource, resourceRequestConfig.pathSeparator)}` : `CDPATH`; - resourceCompletions.push({ - label, - provider, - kind: TerminalCompletionItemKind.Folder, - isDirectory: child.isDirectory, - isFile: child.isFile, - detail, - replacementIndex: cursorPosition - lastWord.length, - replacementLength: lastWord.length - }); } - } - } catch { /* ignore */ } + } catch { /* ignore */ } + } } } } - } - // Add parent directory to the bottom of the list because it's not as useful as other suggestions - // - // For example: - // - `|` -> `../` - // - `./src/|` -> `./src/../` - // - // On Windows, the path seprators are normalized to `\`: - // - `./src/|` -> `.\src\..\` - if (!isAbsolutePath && foldersRequested) { - const parentDir = URI.joinPath(cwd, '..' + resourceRequestConfig.pathSeparator); - resourceCompletions.push({ - label: lastWordFolder + '..' + resourceRequestConfig.pathSeparator, - provider, - kind: TerminalCompletionItemKind.Folder, - detail: getFriendlyPath(parentDir, resourceRequestConfig.pathSeparator), - isDirectory: true, - isFile: false, - replacementIndex: cursorPosition - lastWord.length, - replacementLength: lastWord.length - }); + // Add parent directory to the bottom of the list because it's not as useful as other suggestions + // + // For example: + // - `|` -> `../` + // - `./src/|` -> `./src/../` + // + // On Windows, the path seprators are normalized to `\`: + // - `./src/|` -> `.\src\..\` + if (foldersRequested) { + const parentDir = URI.joinPath(cwd, '..' + resourceRequestConfig.pathSeparator); + resourceCompletions.push({ + label: lastWordFolder + '..' + resourceRequestConfig.pathSeparator, + provider, + kind: TerminalCompletionItemKind.Folder, + detail: getFriendlyPath(parentDir, resourceRequestConfig.pathSeparator), + isDirectory: true, + isFile: false, + replacementIndex: cursorPosition - lastWord.length, + replacementLength: lastWord.length + }); + } + } return resourceCompletions.length ? resourceCompletions : undefined; diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index 92d723eb8d9a..45b3d8af9a47 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -241,23 +241,67 @@ suite('TerminalCompletionService', () => { childResources = []; }); - if (!isWindows) { - test('/usr/| Missing . should show correct results', async () => { + if (isWindows) { + test('C:/Foo/| absolute paths on Windows', async () => { + const resourceRequestConfig: TerminalResourceRequestConfig = { + cwd: URI.parse('file:///C:/Foo'), + foldersRequested: true, + pathSeparator, + shouldNormalizePrefix: true, + }; + validResources = [URI.parse('file:///C:/Foo')]; // Updated to reflect new cwd + childResources = [ + { resource: URI.parse('file:///C:/Foo/Bar'), isDirectory: true, isFile: false }, + { resource: URI.parse('file:///C:/Foo/Baz.txt'), isDirectory: false, isFile: true } + ]; + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'C:/Foo/', 7, provider, capabilities); + + assertCompletions(result, [ + { label: 'C:/Foo/', detail: 'C:/Foo/' }, + { label: 'C:/Foo/Bar/', detail: 'C:/Foo/Bar/' }, + ], { replacementIndex: 0, replacementLength: 7 }); + }); + test('c:/foo/| case sensitivity on Windows', async () => { + const resourceRequestConfig: TerminalResourceRequestConfig = { + cwd: URI.parse('file:///c:/foo'), + foldersRequested: true, + pathSeparator, + shouldNormalizePrefix: true, + }; + validResources = [URI.parse('file:///c:/foo')]; // Updated to reflect new cwd + childResources = [ + { resource: URI.parse('file:///c:/foo/Bar'), isDirectory: true, isFile: false } + ]; + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'c:/foo/', 7, provider, capabilities); + + assertCompletions(result, [ + // Note that the detail is normalizes drive letters to capital case intentionally + { label: 'c:/foo/', detail: 'C:/foo/' }, + { label: 'c:/foo/Bar/', detail: 'C:/foo/Bar/' }, + ], { replacementIndex: 0, replacementLength: 7 }); + }); + } else { + test.skip('/Foo/| absolute paths NOT on Windows', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { cwd: URI.parse('file:///'), foldersRequested: true, pathSeparator, shouldNormalizePrefix: true }; - validResources = [URI.parse('file:///usr')]; - childResources = []; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '/usr/', 5, provider, capabilities); + validResources = [URI.parse('file:///Foo')]; // Updated to reflect new cwd + childResources = [ + { resource: URI.parse('file:///Foo/Bar'), isDirectory: true, isFile: false }, + { resource: URI.parse('file:///Foo/Baz.txt'), isDirectory: false, isFile: true } + ]; + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '/Foo/', 5, provider, capabilities); assertCompletions(result, [ - { label: '/usr/', detail: '/' }, + { label: '/Foo/', detail: '/Foo/' }, + { label: '/Foo/Bar/', detail: '/Foo/Bar/' }, ], { replacementIndex: 0, replacementLength: 5 }); }); } + if (isWindows) { test('.\\folder | Case insensitivity should resolve correctly on Windows', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { From 5446a5a61616ae45d6f40dd14e86b23c8b5caed9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 09:08:09 -0800 Subject: [PATCH 0280/2632] Add case sensitivity test case for non windows --- .../browser/terminalCompletionService.test.ts | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index 45b3d8af9a47..c446aacfdf35 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -261,7 +261,7 @@ suite('TerminalCompletionService', () => { { label: 'C:/Foo/Bar/', detail: 'C:/Foo/Bar/' }, ], { replacementIndex: 0, replacementLength: 7 }); }); - test('c:/foo/| case sensitivity on Windows', async () => { + test('c:/foo/| case insensitivity on Windows', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { cwd: URI.parse('file:///c:/foo'), foldersRequested: true, @@ -281,25 +281,40 @@ suite('TerminalCompletionService', () => { ], { replacementIndex: 0, replacementLength: 7 }); }); } else { - test.skip('/Foo/| absolute paths NOT on Windows', async () => { + test('/foo/| absolute paths NOT on Windows', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { cwd: URI.parse('file:///'), foldersRequested: true, pathSeparator, shouldNormalizePrefix: true }; - validResources = [URI.parse('file:///Foo')]; // Updated to reflect new cwd + validResources = [URI.parse('file:///foo')]; // Updated to reflect new cwd childResources = [ - { resource: URI.parse('file:///Foo/Bar'), isDirectory: true, isFile: false }, - { resource: URI.parse('file:///Foo/Baz.txt'), isDirectory: false, isFile: true } + { resource: URI.parse('file:///foo/Bar'), isDirectory: true, isFile: false }, + { resource: URI.parse('file:///foo/Baz.txt'), isDirectory: false, isFile: true } ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '/Foo/', 5, provider, capabilities); + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '/foo/', 5, provider, capabilities); assertCompletions(result, [ - { label: '/Foo/', detail: '/Foo/' }, - { label: '/Foo/Bar/', detail: '/Foo/Bar/' }, + { label: '/foo/', detail: '/foo/' }, + { label: '/foo/Bar/', detail: '/foo/Bar/' }, ], { replacementIndex: 0, replacementLength: 5 }); }); + test('/foo/| case insensitivity NOT on Windows', async () => { + const resourceRequestConfig: TerminalResourceRequestConfig = { + cwd: URI.parse('file:///foo'), + foldersRequested: true, + pathSeparator, + shouldNormalizePrefix: true, + }; + validResources = [URI.parse('file:///foo')]; // Updated to reflect new cwd + childResources = [ + { resource: URI.parse('file:///foo/Bar'), isDirectory: true, isFile: false } + ]; + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '/Foo/', 5, provider, capabilities); + + assertCompletions(result, [], { replacementIndex: 0, replacementLength: 7 }); + }); } if (isWindows) { From 53bfcd14b299465373ecf82d41757941ab12cbca Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 09:13:32 -0800 Subject: [PATCH 0281/2632] Set kind correctly for children Fixes #239408 --- .../suggest/browser/terminalCompletionService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index 36d4de75fcd4..84b6e595e73e 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -340,7 +340,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo label, provider, kind, - detail: getFriendlyPath(stat.resource, resourceRequestConfig.pathSeparator, TerminalCompletionItemKind.File), + detail: getFriendlyPath(stat.resource, resourceRequestConfig.pathSeparator, kind), isDirectory, isFile: kind === TerminalCompletionItemKind.File, replacementIndex: cursorPosition - lastWord.length, From 1fec88a0a63df190caf637357740919de96f75cb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 09:28:16 -0800 Subject: [PATCH 0282/2632] Fix typo, use more specific type --- .../services/suggest/browser/simpleCompletionItem.ts | 7 +++++-- .../services/suggest/browser/simpleCompletionModel.ts | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts b/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts index 5bfb52723431..b1301167f1a0 100644 --- a/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts +++ b/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts @@ -74,7 +74,10 @@ export class SimpleCompletionItem { */ readonly labelLowNormalizedPath: string; - readonly unscorePenalty: number = 0; + /** + * A penalty that applies to files or folders starting with the underscore character. + */ + readonly underscorePenalty: 0 | 1 = 0; /** * The file extension part from {@link labelLow}. @@ -112,7 +115,7 @@ export class SimpleCompletionItem { if (completion.isDirectory) { this.labelLowNormalizedPath = this.labelLowNormalizedPath.replace(/\/$/, ''); } - this.unscorePenalty = basename(this.labelLowNormalizedPath).startsWith('_') ? 1 : 0; + this.underscorePenalty = basename(this.labelLowNormalizedPath).startsWith('_') ? 1 : 0; } } } diff --git a/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts b/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts index 4e59fc802e89..4031bba7bae3 100644 --- a/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts +++ b/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts @@ -215,9 +215,9 @@ export class SimpleCompletionModel { } } - // Sort by unscore penalty (eg. `__init__/` should be penalized) - if (a.unscorePenalty !== b.unscorePenalty) { - return a.unscorePenalty - b.unscorePenalty; + // Sort by underscore penalty (eg. `__init__/` should be penalized) + if (a.underscorePenalty !== b.underscorePenalty) { + return a.underscorePenalty - b.underscorePenalty; } // Sort by folder depth (eg. `vscode/` should come before `vscode-.../`) From b900a008adefb16db4c692755a5f26cb59317da2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 11:16:34 -0800 Subject: [PATCH 0283/2632] Get absolute paths working on mac --- .../suggest/browser/terminalCompletionService.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index 4eb91c8ef62b..58fb4ff10a3b 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -223,7 +223,8 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo const resourceCompletions: ITerminalCompletion[] = []; const cursorPrefix = promptValue.substring(0, cursorPosition); - const useForwardSlash = !resourceRequestConfig.shouldNormalizePrefix && isWindows; + // TODO: This should come in through the resourceRequestConfig + const useBackslash = isWindows; // The last word (or argument). When the cursor is following a space it will be the empty // string @@ -232,7 +233,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo // Get the nearest folder path from the prefix. This ignores everything after the `/` as // they are what triggers changes in the directory. let lastSlashIndex: number; - if (useForwardSlash) { + if (useBackslash) { lastSlashIndex = Math.max(lastWord.lastIndexOf('\\'), lastWord.lastIndexOf('/')); } else { lastSlashIndex = lastWord.lastIndexOf(resourceRequestConfig.pathSeparator); @@ -269,9 +270,9 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo // Handle absolute paths differently to avoid adding `./` prefixes // TODO: Deal with git bash case - const isAbsolutePath = useForwardSlash - ? lastWord.startsWith(resourceRequestConfig.pathSeparator) && lastWord.endsWith(resourceRequestConfig.pathSeparator) - : /^[a-zA-Z]:[\\\/]/.test(lastWord); + const isAbsolutePath = useBackslash + ? /^[a-zA-Z]:[\\\/]/.test(lastWord) + : lastWord.startsWith(resourceRequestConfig.pathSeparator); if (isAbsolutePath) { @@ -397,7 +398,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo // Normalize path separator to `\` on Windows. It should act the exact same as `/` but // suggestions should all use `\` - if (useForwardSlash) { + if (useBackslash) { label = label.replaceAll('/', '\\'); } @@ -419,7 +420,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo if (config === 'absolute' || config === 'relative') { const cdPath = capabilities.get(TerminalCapability.ShellEnvDetection)?.env?.get('CDPATH'); if (cdPath) { - const cdPathEntries = cdPath.split(useForwardSlash ? ';' : ':'); + const cdPathEntries = cdPath.split(useBackslash ? ';' : ':'); for (const cdPathEntry of cdPathEntries) { try { const fileStat = await this._fileService.resolve(URI.file(cdPathEntry), { resolveSingleChildDescendants: true }); From 61f0892200b19b315e248fca8d1ac896715039be Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 11:24:01 -0800 Subject: [PATCH 0284/2632] Fix detail for absolute paths --- .../browser/terminalCompletionService.ts | 5 +++-- .../browser/terminalCompletionService.test.ts | 21 +++---------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index 58fb4ff10a3b..9d1865bc3d78 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -276,7 +276,8 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo if (isAbsolutePath) { - const fileStat = await this._fileService.resolve(URI.file(lastWordFolder), { resolveSingleChildDescendants: true }); + const lastWordResource = URI.file(lastWordFolder); + const fileStat = await this._fileService.resolve(lastWordResource, { resolveSingleChildDescendants: true }); if (!fileStat?.children) { return; } @@ -294,7 +295,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo kind: TerminalCompletionItemKind.Folder, isDirectory: true, isFile: false, - detail: getFriendlyPath(cwd, resourceRequestConfig.pathSeparator), + detail: getFriendlyPath(lastWordResource, resourceRequestConfig.pathSeparator), replacementIndex: cursorPosition - lastWord.length, replacementLength: lastWord.length }); diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index c446aacfdf35..f5dfbda038ad 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -249,7 +249,7 @@ suite('TerminalCompletionService', () => { pathSeparator, shouldNormalizePrefix: true, }; - validResources = [URI.parse('file:///C:/Foo')]; // Updated to reflect new cwd + validResources = [URI.parse('file:///C:/Foo')]; childResources = [ { resource: URI.parse('file:///C:/Foo/Bar'), isDirectory: true, isFile: false }, { resource: URI.parse('file:///C:/Foo/Baz.txt'), isDirectory: false, isFile: true } @@ -268,7 +268,7 @@ suite('TerminalCompletionService', () => { pathSeparator, shouldNormalizePrefix: true, }; - validResources = [URI.parse('file:///c:/foo')]; // Updated to reflect new cwd + validResources = [URI.parse('file:///c:/foo')]; childResources = [ { resource: URI.parse('file:///c:/foo/Bar'), isDirectory: true, isFile: false } ]; @@ -288,7 +288,7 @@ suite('TerminalCompletionService', () => { pathSeparator, shouldNormalizePrefix: true }; - validResources = [URI.parse('file:///foo')]; // Updated to reflect new cwd + validResources = [URI.parse('file:///foo')]; childResources = [ { resource: URI.parse('file:///foo/Bar'), isDirectory: true, isFile: false }, { resource: URI.parse('file:///foo/Baz.txt'), isDirectory: false, isFile: true } @@ -300,21 +300,6 @@ suite('TerminalCompletionService', () => { { label: '/foo/Bar/', detail: '/foo/Bar/' }, ], { replacementIndex: 0, replacementLength: 5 }); }); - test('/foo/| case insensitivity NOT on Windows', async () => { - const resourceRequestConfig: TerminalResourceRequestConfig = { - cwd: URI.parse('file:///foo'), - foldersRequested: true, - pathSeparator, - shouldNormalizePrefix: true, - }; - validResources = [URI.parse('file:///foo')]; // Updated to reflect new cwd - childResources = [ - { resource: URI.parse('file:///foo/Bar'), isDirectory: true, isFile: false } - ]; - const result = await terminalCompletionService.resolveResources(resourceRequestConfig, '/Foo/', 5, provider, capabilities); - - assertCompletions(result, [], { replacementIndex: 0, replacementLength: 7 }); - }); } if (isWindows) { From f90911587074f5a78c53391eec973f019cb836d3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Feb 2025 11:26:02 -0800 Subject: [PATCH 0285/2632] Use a different cwd in tests to confirm cwd isn't used in completions --- .../suggest/test/browser/terminalCompletionService.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index f5dfbda038ad..00325ad53699 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -244,7 +244,7 @@ suite('TerminalCompletionService', () => { if (isWindows) { test('C:/Foo/| absolute paths on Windows', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { - cwd: URI.parse('file:///C:/Foo'), + cwd: URI.parse('file:///C:'), foldersRequested: true, pathSeparator, shouldNormalizePrefix: true, @@ -263,7 +263,7 @@ suite('TerminalCompletionService', () => { }); test('c:/foo/| case insensitivity on Windows', async () => { const resourceRequestConfig: TerminalResourceRequestConfig = { - cwd: URI.parse('file:///c:/foo'), + cwd: URI.parse('file:///c:'), foldersRequested: true, pathSeparator, shouldNormalizePrefix: true, From 53790287ceb2cf62383e4b5ef5f86b1681c35fc0 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Feb 2025 04:27:47 -0800 Subject: [PATCH 0286/2632] Improve names on PathExecutableCache --- .../src/env/pathExecutableCache.ts | 26 +++++++++---------- .../src/terminalSuggestMain.ts | 2 +- .../src/test/env/pathExecutableCache.test.ts | 6 ++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/extensions/terminal-suggest/src/env/pathExecutableCache.ts b/extensions/terminal-suggest/src/env/pathExecutableCache.ts index 15f1bbcb694e..4ce8090f088f 100644 --- a/extensions/terminal-suggest/src/env/pathExecutableCache.ts +++ b/extensions/terminal-suggest/src/env/pathExecutableCache.ts @@ -16,17 +16,17 @@ const isWindows = osIsWindows(); export class PathExecutableCache implements vscode.Disposable { private _disposables: vscode.Disposable[] = []; - private _cachedAvailableCommandsPath: string | undefined; - private _cachedWindowsExecutableExtensions: { [key: string]: boolean | undefined } | undefined; - private _cachedCommandsInPath: { completionResources: Set | undefined; labels: Set | undefined } | undefined; + private _cachedPathValue: string | undefined; + private _cachedWindowsExeExtensions: { [key: string]: boolean | undefined } | undefined; + private _cachedExes: { completionResources: Set | undefined; labels: Set | undefined } | undefined; constructor() { if (isWindows) { - this._cachedWindowsExecutableExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly); + this._cachedWindowsExeExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly); this._disposables.push(vscode.workspace.onDidChangeConfiguration(e => { if (e.affectsConfiguration(SettingsIds.CachedWindowsExecutableExtensions)) { - this._cachedWindowsExecutableExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly); - this._cachedCommandsInPath = undefined; + this._cachedWindowsExeExtensions = vscode.workspace.getConfiguration(SettingsIds.SuggestPrefix).get(SettingsIds.CachedWindowsExecutableExtensionsSuffixOnly); + this._cachedExes = undefined; } })); } @@ -38,7 +38,7 @@ export class PathExecutableCache implements vscode.Disposable { } } - async getCommandsInPath(env: { [key: string]: string | undefined } = process.env): Promise<{ completionResources: Set | undefined; labels: Set | undefined } | undefined> { + async getExecutablesInPath(env: { [key: string]: string | undefined } = process.env): Promise<{ completionResources: Set | undefined; labels: Set | undefined } | undefined> { // Create cache key let pathValue: string | undefined; if (isWindows) { @@ -54,8 +54,8 @@ export class PathExecutableCache implements vscode.Disposable { } // Check cache - if (this._cachedCommandsInPath && this._cachedAvailableCommandsPath === pathValue) { - return this._cachedCommandsInPath; + if (this._cachedExes && this._cachedPathValue === pathValue) { + return this._cachedExes; } // Extract executables from PATH @@ -79,9 +79,9 @@ export class PathExecutableCache implements vscode.Disposable { } // Return - this._cachedAvailableCommandsPath = pathValue; - this._cachedCommandsInPath = { completionResources: executables, labels }; - return this._cachedCommandsInPath; + this._cachedPathValue = pathValue; + this._cachedExes = { completionResources: executables, labels }; + return this._cachedExes; } private async _getFilesInPath(path: string, pathSeparator: string, labels: Set): Promise | undefined> { @@ -95,7 +95,7 @@ export class PathExecutableCache implements vscode.Disposable { const files = await vscode.workspace.fs.readDirectory(fileResource); for (const [file, fileType] of files) { const formattedPath = getFriendlyResourcePath(vscode.Uri.joinPath(fileResource, file), pathSeparator); - if (!labels.has(file) && fileType !== vscode.FileType.Unknown && fileType !== vscode.FileType.Directory && await isExecutable(formattedPath, this._cachedWindowsExecutableExtensions)) { + if (!labels.has(file) && fileType !== vscode.FileType.Unknown && fileType !== vscode.FileType.Directory && await isExecutable(formattedPath, this._cachedWindowsExeExtensions)) { result.add({ label: file, detail: formattedPath }); labels.add(file); } diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 808e26a9c6b7..d5119697098e 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -96,7 +96,7 @@ export async function activate(context: vscode.ExtensionContext) { return; } - const commandsInPath = await pathExecutableCache.getCommandsInPath(terminal.shellIntegration?.env); + const commandsInPath = await pathExecutableCache.getExecutablesInPath(terminal.shellIntegration?.env); const shellGlobals = await getShellGlobals(shellType, commandsInPath?.labels) ?? []; if (!commandsInPath?.completionResources) { return; diff --git a/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts b/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts index 4ba5451eafc2..890aa1010333 100644 --- a/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts +++ b/extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts @@ -10,7 +10,7 @@ import { PathExecutableCache } from '../../env/pathExecutableCache'; suite('PathExecutableCache', () => { test('cache should return empty for empty PATH', async () => { const cache = new PathExecutableCache(); - const result = await cache.getCommandsInPath({ PATH: '' }); + const result = await cache.getExecutablesInPath({ PATH: '' }); strictEqual(Array.from(result!.completionResources!).length, 0); strictEqual(Array.from(result!.labels!).length, 0); }); @@ -18,8 +18,8 @@ suite('PathExecutableCache', () => { test('caching is working on successive calls', async () => { const cache = new PathExecutableCache(); const env = { PATH: process.env.PATH }; - const result = await cache.getCommandsInPath(env); - const result2 = await cache.getCommandsInPath(env); + const result = await cache.getExecutablesInPath(env); + const result2 = await cache.getExecutablesInPath(env); strictEqual(result, result2); }); }); From 521e89838af1a28c39bd119dbb449d76bb73a210 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Feb 2025 04:40:31 -0800 Subject: [PATCH 0287/2632] Don't show fig information for aliases Fixes #239425 --- extensions/terminal-suggest/src/shell/bash.ts | 1 + extensions/terminal-suggest/src/shell/fish.ts | 2 +- extensions/terminal-suggest/src/shell/pwsh.ts | 1 + extensions/terminal-suggest/src/shell/zsh.ts | 1 + extensions/terminal-suggest/src/terminalSuggestMain.ts | 6 ++++-- extensions/terminal-suggest/src/types.ts | 5 +++++ 6 files changed, 13 insertions(+), 3 deletions(-) diff --git a/extensions/terminal-suggest/src/shell/bash.ts b/extensions/terminal-suggest/src/shell/bash.ts index b440af3c8805..96d3b3765b23 100644 --- a/extensions/terminal-suggest/src/shell/bash.ts +++ b/extensions/terminal-suggest/src/shell/bash.ts @@ -37,6 +37,7 @@ async function getAliases(options: ExecOptionsWithStringEncoding): Promise[a-zA-Z0-9\.:-]+) (?.+)$/); @@ -38,6 +37,7 @@ async function getAliases(options: ExecOptionsWithStringEncoding): Promise command.label === specLabel); + const availableCommand = availableCommands.find(command => specLabel === (command.definition ?? command.label)); if (!availableCommand || (token && token.isCancellationRequested)) { continue; } // push it to the completion items if (tokenType === TokenType.Command) { - items.push(createCompletionItem(terminalContext.cursorPosition, prefix, { label: specLabel }, getDescription(spec), availableCommand.detail)); + if (availableCommand.kind !== vscode.TerminalCompletionItemKind.Alias) { + items.push(createCompletionItem(terminalContext.cursorPosition, prefix, { label: specLabel }, getDescription(spec), availableCommand.detail)); + } continue; } diff --git a/extensions/terminal-suggest/src/types.ts b/extensions/terminal-suggest/src/types.ts index 43b86a08a93e..6e9ec6c5f140 100644 --- a/extensions/terminal-suggest/src/types.ts +++ b/extensions/terminal-suggest/src/types.ts @@ -7,6 +7,11 @@ import * as vscode from 'vscode'; export interface ICompletionResource { label: string; + /** + * The definition of the completion, this will be the resolved value of an + * alias completion. + */ + definition?: string; detail?: string; kind?: vscode.TerminalCompletionItemKind; } From c4cf1864d2fbea6ac48e0219211cc53c26cd4382 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Feb 2025 05:15:25 -0800 Subject: [PATCH 0288/2632] Resolve fig spec against alias definition command --- extensions/terminal-suggest/src/shell/bash.ts | 8 +++++++- extensions/terminal-suggest/src/shell/fish.ts | 8 +++++++- extensions/terminal-suggest/src/shell/pwsh.ts | 10 +++++++++- extensions/terminal-suggest/src/shell/zsh.ts | 8 +++++++- extensions/terminal-suggest/src/terminalSuggestMain.ts | 5 +++-- extensions/terminal-suggest/src/types.ts | 6 +++--- 6 files changed, 36 insertions(+), 9 deletions(-) diff --git a/extensions/terminal-suggest/src/shell/bash.ts b/extensions/terminal-suggest/src/shell/bash.ts index 96d3b3765b23..c4ce4598cb39 100644 --- a/extensions/terminal-suggest/src/shell/bash.ts +++ b/extensions/terminal-suggest/src/shell/bash.ts @@ -33,11 +33,17 @@ async function getAliases(options: ExecOptionsWithStringEncoding): Promise specLabel === (command.definition ?? command.label)); + const availableCommand = availableCommands.find(command => specLabel === command.label); if (!availableCommand || (token && token.isCancellationRequested)) { continue; } @@ -262,7 +262,8 @@ export async function getCompletionItemsFromSpecs( continue; } - if (!terminalContext.commandLine.startsWith(`${specLabel} `)) { + const commandAndAliases = availableCommands.filter(command => specLabel === (command.definitionCommand ?? command.label)); + if (!commandAndAliases.some(e => terminalContext.commandLine.startsWith(`${e.label} `))) { // the spec label is not the first word in the command line, so do not provide options or args continue; } diff --git a/extensions/terminal-suggest/src/types.ts b/extensions/terminal-suggest/src/types.ts index 6e9ec6c5f140..abc4f4f291b4 100644 --- a/extensions/terminal-suggest/src/types.ts +++ b/extensions/terminal-suggest/src/types.ts @@ -8,10 +8,10 @@ import * as vscode from 'vscode'; export interface ICompletionResource { label: string; /** - * The definition of the completion, this will be the resolved value of an - * alias completion. + * The definition command of the completion, this will be the resolved value of an alias + * completion. */ - definition?: string; + definitionCommand?: string; detail?: string; kind?: vscode.TerminalCompletionItemKind; } From 4f587d78c2ecd5d5d4100aa473ed647ba267331e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Feb 2025 05:18:45 -0800 Subject: [PATCH 0289/2632] Pull alias parsing into helper --- extensions/terminal-suggest/src/shell/bash.ts | 29 ++----------------- .../terminal-suggest/src/shell/common.ts | 29 +++++++++++++++++++ extensions/terminal-suggest/src/shell/fish.ts | 29 ++----------------- extensions/terminal-suggest/src/shell/zsh.ts | 29 ++----------------- 4 files changed, 35 insertions(+), 81 deletions(-) diff --git a/extensions/terminal-suggest/src/shell/bash.ts b/extensions/terminal-suggest/src/shell/bash.ts index c4ce4598cb39..bb7e374bed21 100644 --- a/extensions/terminal-suggest/src/shell/bash.ts +++ b/extensions/terminal-suggest/src/shell/bash.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; import { type ExecOptionsWithStringEncoding } from 'node:child_process'; -import { execHelper, spawnHelper } from './common'; +import { execHelper, getAliasesHelper } from './common'; export async function getBashGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { return [ @@ -22,29 +21,5 @@ async function getBuiltins(options: ExecOptionsWithStringEncoding, existingComma } async function getAliases(options: ExecOptionsWithStringEncoding): Promise { - // This must be run with interactive, otherwise there's a good chance aliases won't - // be set up. Note that this could differ from the actual aliases as it's a new bash - // session, for the same reason this would not include aliases that are created - // by simply running `alias ...` in the terminal. - const aliasOutput = await spawnHelper('bash', ['-ic', 'alias'], options); - const result: ICompletionResource[] = []; - for (const line of aliasOutput.split('\n')) { - const match = line.match(/^alias (?[a-zA-Z0-9\.:-]+)='(?.+)'$/); - if (!match?.groups) { - continue; - } - let definitionCommand = ''; - let definitionIndex = match.groups.resolved.indexOf(' '); - if (definitionIndex === -1) { - definitionIndex = match.groups.resolved.length; - } - definitionCommand = match.groups.resolved.substring(0, definitionIndex); - result.push({ - label: match.groups.alias, - detail: match.groups.resolved, - kind: vscode.TerminalCompletionItemKind.Alias, - definitionCommand, - }); - } - return result; + return getAliasesHelper('bash', ['-ic', 'alias'], /^alias (?[a-zA-Z0-9\.:-]+)='(?.+)'$/, options); } diff --git a/extensions/terminal-suggest/src/shell/common.ts b/extensions/terminal-suggest/src/shell/common.ts index 590195e3600b..5ef609002a6c 100644 --- a/extensions/terminal-suggest/src/shell/common.ts +++ b/extensions/terminal-suggest/src/shell/common.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; import { exec, spawn, type ExecOptionsWithStringEncoding } from 'node:child_process'; +import type { ICompletionResource } from '../types'; export async function spawnHelper(command: string, args: string[], options: ExecOptionsWithStringEncoding): Promise { // This must be run with interactive, otherwise there's a good chance aliases won't @@ -38,3 +40,30 @@ export async function execHelper(commandLine: string, options: ExecOptionsWithSt }); } +export async function getAliasesHelper(command: string, args: string[], regex: RegExp, options: ExecOptionsWithStringEncoding): Promise { + // This must be run with interactive, otherwise there's a good chance aliases won't + // be set up. Note that this could differ from the actual aliases as it's a new bash + // session, for the same reason this would not include aliases that are created + // by simply running `alias ...` in the terminal. + const aliasOutput = await spawnHelper(command, args, options); + const result: ICompletionResource[] = []; + for (const line of aliasOutput.split('\n')) { + const match = line.match(regex); + if (!match?.groups) { + continue; + } + let definitionCommand = ''; + let definitionIndex = match.groups.resolved.indexOf(' '); + if (definitionIndex === -1) { + definitionIndex = match.groups.resolved.length; + } + definitionCommand = match.groups.resolved.substring(0, definitionIndex); + result.push({ + label: match.groups.alias, + detail: match.groups.resolved, + kind: vscode.TerminalCompletionItemKind.Alias, + definitionCommand, + }); + } + return result; +} diff --git a/extensions/terminal-suggest/src/shell/fish.ts b/extensions/terminal-suggest/src/shell/fish.ts index 5341a5e5a4f6..cc2fbaff6e0c 100644 --- a/extensions/terminal-suggest/src/shell/fish.ts +++ b/extensions/terminal-suggest/src/shell/fish.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; -import { execHelper, spawnHelper } from './common'; +import { execHelper, getAliasesHelper } from './common'; import { type ExecOptionsWithStringEncoding } from 'node:child_process'; export async function getFishGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { @@ -22,29 +21,5 @@ async function getBuiltins(options: ExecOptionsWithStringEncoding, existingComma } async function getAliases(options: ExecOptionsWithStringEncoding): Promise { - // This must be run with interactive, otherwise there's a good chance aliases won't - // be set up. Note that this could differ from the actual aliases as it's a new bash - // session, for the same reason this would not include aliases that are created - // by simply running `alias ...` in the terminal. - const aliasOutput = await spawnHelper('fish', ['-ic', 'alias'], options); - const result: ICompletionResource[] = []; - for (const line of aliasOutput.split('\n')) { - const match = line.match(/^alias (?[a-zA-Z0-9\.:-]+) (?.+)$/); - if (!match?.groups) { - continue; - } - let definitionCommand = ''; - let definitionIndex = match.groups.resolved.indexOf(' '); - if (definitionIndex === -1) { - definitionIndex = match.groups.resolved.length; - } - definitionCommand = match.groups.resolved.substring(0, definitionIndex); - result.push({ - label: match.groups.alias, - detail: match.groups.resolved, - kind: vscode.TerminalCompletionItemKind.Alias, - definitionCommand, - }); - } - return result; + return getAliasesHelper('fish', ['-ic', 'alias'], /^alias (?[a-zA-Z0-9\.:-]+) (?.+)$/, options); } diff --git a/extensions/terminal-suggest/src/shell/zsh.ts b/extensions/terminal-suggest/src/shell/zsh.ts index eb04464be68d..1530c5de5865 100644 --- a/extensions/terminal-suggest/src/shell/zsh.ts +++ b/extensions/terminal-suggest/src/shell/zsh.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; import type { ICompletionResource } from '../types'; -import { execHelper, spawnHelper } from './common'; +import { execHelper, getAliasesHelper } from './common'; import { type ExecOptionsWithStringEncoding } from 'node:child_process'; export async function getZshGlobals(options: ExecOptionsWithStringEncoding, existingCommands?: Set): Promise<(string | ICompletionResource)[]> { @@ -22,29 +21,5 @@ async function getBuiltins(options: ExecOptionsWithStringEncoding, existingComma } async function getAliases(options: ExecOptionsWithStringEncoding): Promise { - // This must be run with interactive, otherwise there's a good chance aliases won't - // be set up. Note that this could differ from the actual aliases as it's a new bash - // session, for the same reason this would not include aliases that are created - // by simply running `alias ...` in the terminal. - const aliasOutput = await spawnHelper('zsh', ['-ic', 'alias'], options); - const result: ICompletionResource[] = []; - for (const line of aliasOutput.split('\n')) { - const match = line.match(/^(?[a-zA-Z0-9\.:-]+)=(?:'(?.+)'|(?.+))$/); - if (!match?.groups) { - continue; - } - let definitionCommand = ''; - let definitionIndex = match.groups.resolved.indexOf(' '); - if (definitionIndex === -1) { - definitionIndex = match.groups.resolved.length; - } - definitionCommand = match.groups.resolved.substring(0, definitionIndex); - result.push({ - label: match.groups.alias, - detail: match.groups.resolved, - kind: vscode.TerminalCompletionItemKind.Alias, - definitionCommand, - }); - } - return result; + return getAliasesHelper('zsh', ['-ic', 'alias'], /^(?[a-zA-Z0-9\.:-]+)=(?:'(?.+)'|(?.+))$/, options); } From d1e94453d27b840729cce517bbe99cf1b8d92eaa Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Sun, 2 Feb 2025 14:34:08 -0300 Subject: [PATCH 0290/2632] Refactors observable logging (#239430) --- .../base/common/observableInternal/autorun.ts | 54 +++--- src/vs/base/common/observableInternal/base.ts | 72 +++++--- .../base/common/observableInternal/derived.ts | 25 +-- .../base/common/observableInternal/index.ts | 10 +- .../observableInternal/lazyObservableValue.ts | 6 +- .../consoleObservableLogger.ts} | 169 ++++++++---------- .../observableInternal/logging/logging.ts | 129 +++++++++++++ .../base/common/observableInternal/utils.ts | 10 +- 8 files changed, 300 insertions(+), 175 deletions(-) rename src/vs/base/common/observableInternal/{logging.ts => logging/consoleObservableLogger.ts} (70%) create mode 100644 src/vs/base/common/observableInternal/logging/logging.ts diff --git a/src/vs/base/common/observableInternal/autorun.ts b/src/vs/base/common/observableInternal/autorun.ts index d2425e110121..704353fe6cc9 100644 --- a/src/vs/base/common/observableInternal/autorun.ts +++ b/src/vs/base/common/observableInternal/autorun.ts @@ -6,7 +6,7 @@ import { IChangeContext, IObservable, IObservableWithChange, IObserver, IReader } from './base.js'; import { DebugNameData, IDebugNameData } from './debugName.js'; import { assertFn, BugIndicatingError, DisposableStore, IDisposable, markAsDisposed, onBugIndicatingError, toDisposable, trackDisposable } from './commonFacade/deps.js'; -import { getLogger } from './logging.js'; +import { getLogger } from './logging/logging.js'; /** * Runs immediately and whenever a transaction ends and an observed observable changed. @@ -153,9 +153,7 @@ export function autorunIterableDelta( }); } - - -const enum AutorunState { +export const enum AutorunState { /** * A dependency could have changed. * We need to explicitly ask them if at least one dependency changed. @@ -176,6 +174,7 @@ export class AutorunObserver implements IObserver, IReader private dependencies = new Set>(); private dependenciesToBeRemoved = new Set>(); private changeSummary: TChangeSummary | undefined; + private _isRunning = false; public get debugName(): string { return this._debugNameData.getDebugName(this) ?? '(anonymous)'; @@ -197,10 +196,11 @@ export class AutorunObserver implements IObserver, IReader public dispose(): void { this.disposed = true; for (const o of this.dependencies) { - o.removeObserver(this); + o.removeObserver(this); // Warning: external call! } this.dependencies.clear(); + getLogger()?.handleAutorunDisposed(this); markAsDisposed(this); } @@ -215,29 +215,28 @@ export class AutorunObserver implements IObserver, IReader this.state = AutorunState.upToDate; - const isDisposed = this.disposed; try { - if (!isDisposed) { - getLogger()?.handleAutorunTriggered(this); + if (!this.disposed) { + getLogger()?.handleAutorunStarted(this); const changeSummary = this.changeSummary!; try { - this.changeSummary = this.createChangeSummary?.(); - this._isReaderValid = true; - this._runFn(this, changeSummary); + this.changeSummary = this.createChangeSummary?.(); // Warning: external call! + this._isRunning = true; + this._runFn(this, changeSummary); // Warning: external call! } catch (e) { onBugIndicatingError(e); } finally { - this._isReaderValid = false; + this._isRunning = false; } } } finally { - if (!isDisposed) { + if (!this.disposed) { getLogger()?.handleAutorunFinished(this); } // We don't want our observed observables to think that they are (not even temporarily) not being observed. // Thus, we only unsubscribe from observables that are definitely not read anymore. for (const o of this.dependenciesToBeRemoved) { - o.removeObserver(this); + o.removeObserver(this); // Warning: external call! } this.dependenciesToBeRemoved.clear(); } @@ -248,21 +247,21 @@ export class AutorunObserver implements IObserver, IReader } // IObserver implementation - public beginUpdate(): void { + public beginUpdate(_observable: IObservable): void { if (this.state === AutorunState.upToDate) { this.state = AutorunState.dependenciesMightHaveChanged; } this.updateCount++; } - public endUpdate(): void { + public endUpdate(_observable: IObservable): void { try { if (this.updateCount === 1) { do { if (this.state === AutorunState.dependenciesMightHaveChanged) { this.state = AutorunState.upToDate; for (const d of this.dependencies) { - d.reportChanges(); + d.reportChanges(); // Warning: external call! if (this.state as AutorunState === AutorunState.stale) { // The other dependencies will refresh on demand break; @@ -270,7 +269,7 @@ export class AutorunObserver implements IObserver, IReader } } - this._runIfNeeded(); + this._runIfNeeded(); // Warning: indirect external call! } while (this.state !== AutorunState.upToDate); } } finally { @@ -281,14 +280,16 @@ export class AutorunObserver implements IObserver, IReader } public handlePossibleChange(observable: IObservable): void { - if (this.state === AutorunState.upToDate && this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { + if (this.state === AutorunState.upToDate && this._isDependency(observable)) { this.state = AutorunState.dependenciesMightHaveChanged; } } public handleChange(observable: IObservableWithChange, change: TChange): void { - if (this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { + if (this._isDependency(observable)) { + getLogger()?.handleAutorunDependencyChanged(this, observable, change); try { + // Warning: external call! const shouldReact = this._handleChange ? this._handleChange({ changedObservable: observable, change, @@ -303,19 +304,22 @@ export class AutorunObserver implements IObserver, IReader } } + private _isDependency(observable: IObservableWithChange): boolean { + return this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable); + } + // IReader implementation - private _isReaderValid = false; public readObservable(observable: IObservable): T { - if (!this._isReaderValid) { throw new BugIndicatingError('The reader object cannot be used outside its compute function!'); } + if (!this._isRunning) { throw new BugIndicatingError('The reader object cannot be used outside its compute function!'); } // In case the run action disposes the autorun if (this.disposed) { - return observable.get(); + return observable.get(); // warning: external call! } - observable.addObserver(this); - const value = observable.get(); + observable.addObserver(this); // warning: external call! + const value = observable.get(); // warning: external call! this.dependencies.add(observable); this.dependenciesToBeRemoved.delete(observable); return value; diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index f2aa0466f783..04378d92552d 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -6,7 +6,7 @@ import { DebugNameData, DebugOwner, getFunctionName } from './debugName.js'; import { DisposableStore, EqualityComparer, IDisposable, strictEquals } from './commonFacade/deps.js'; import type { derivedOpts } from './derived.js'; -import { getLogger, logObservable } from './logging.js'; +import { getLogger, logObservable } from './logging/logging.js'; import { keepObserved, recomputeInitiallyAndOnChange } from './utils.js'; /** @@ -14,6 +14,8 @@ import { keepObserved, recomputeInitiallyAndOnChange } from './utils.js'; * * @template T The type of the values the observable can hold. */ +// This interface exists so that, for example for string observables, +// typescript renders the type as `IObservable` instead of `IObservable`. export interface IObservable extends IObservableWithChange { } /** @@ -56,6 +58,8 @@ export interface IObservableWithChange { */ removeObserver(observer: IObserver): void; + // #region These members have a standard implementation and are only part of the interface for convenience. + /** * Reads the current value and subscribes the reader to this observable. * @@ -64,6 +68,16 @@ export interface IObservableWithChange { */ read(reader: IReader | undefined): T; + /** + * Makes sure this value is computed eagerly. + */ + recomputeInitiallyAndOnChange(store: DisposableStore, handleValue?: (value: T) => void): IObservable; + + /** + * Makes sure this value is cached. + */ + keepObserved(store: DisposableStore): IObservable; + /** * Creates a derived observable that depends on this observable. * Use the reader to read other observables @@ -80,16 +94,6 @@ export interface IObservableWithChange { */ log(): IObservableWithChange; - /** - * Makes sure this value is computed eagerly. - */ - recomputeInitiallyAndOnChange(store: DisposableStore, handleValue?: (value: T) => void): IObservable; - - /** - * Makes sure this value is cached. - */ - keepObserved(store: DisposableStore): IObservable; - /** * A human-readable name for debugging purposes. */ @@ -99,13 +103,8 @@ export interface IObservableWithChange { * This property captures the type of the change object. Do not use it at runtime! */ readonly TChange: TChange; -} -export interface IReader { - /** - * Reads the value of an observable and subscribes to it. - */ - readObservable(observable: IObservableWithChange): T; + // #endregion } /** @@ -153,6 +152,13 @@ export interface IObserver { handleChange(observable: IObservableWithChange, change: TChange): void; } +export interface IReader { + /** + * Reads the value of an observable and subscribes to it. + */ + readObservable(observable: IObservableWithChange): T; +} + export interface ISettable { /** * Sets the value of the observable. @@ -182,7 +188,6 @@ export function _setKeepObserved(keepObserved: typeof _keepObserved) { _keepObserved = keepObserved; } - let _derived: typeof derivedOpts; /** * @internal @@ -246,10 +251,7 @@ export abstract class ConvenientObservable implements IObservableWit ); } - public log(): IObservableWithChange { - logObservable(this); - return this; - } + public abstract log(): IObservableWithChange; /** * @sealed @@ -290,12 +292,20 @@ export abstract class ConvenientObservable implements IObservableWit export abstract class BaseObservable extends ConvenientObservable { protected readonly observers = new Set(); + constructor() { + super(); + getLogger()?.handleObservableCreated(this); + } + public addObserver(observer: IObserver): void { const len = this.observers.size; this.observers.add(observer); if (len === 0) { this.onFirstObserverAdded(); } + if (len !== this.observers.size) { + getLogger()?.handleOnListenerCountChanged(this, this.observers.size); + } } public removeObserver(observer: IObserver): void { @@ -303,10 +313,22 @@ export abstract class BaseObservable extends ConvenientObserv if (deleted && this.observers.size === 0) { this.onLastObserverRemoved(); } + if (deleted) { + getLogger()?.handleOnListenerCountChanged(this, this.observers.size); + } } protected onFirstObserverAdded(): void { } protected onLastObserverRemoved(): void { } + + public override log(): IObservableWithChange { + const hadLogger = !!getLogger(); + logObservable(this); + if (!hadLogger) { + getLogger()?.handleObservableCreated(this); + } + return this; + } } /** @@ -390,7 +412,7 @@ export class TransactionImpl implements ITransaction { } // Prevent anyone from updating observers from now on. this.updatingObservers = null; - getLogger()?.handleEndTransaction(); + getLogger()?.handleEndTransaction(this); } } @@ -434,6 +456,8 @@ export class ObservableValue ) { super(); this._value = initialValue; + + getLogger()?.handleObservableUpdated(this, { hadValue: false, newValue: initialValue, change: undefined, didChange: true, oldValue: undefined }); } public override get(): T { return this._value; @@ -451,7 +475,7 @@ export class ObservableValue try { const oldValue = this._value; this._setValue(value); - getLogger()?.handleObservableChanged(this, { oldValue, newValue: value, change, didChange: true, hadValue: true }); + getLogger()?.handleObservableUpdated(this, { oldValue, newValue: value, change, didChange: true, hadValue: true }); for (const observer of this.observers) { tx.updateObserver(observer, this); diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index 54a1e99296db..ce1bbd7878bc 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -6,7 +6,7 @@ import { BaseObservable, IChangeContext, IObservable, IObservableWithChange, IObserver, IReader, ISettableObservable, ITransaction, _setDerivedOpts, } from './base.js'; import { DebugNameData, DebugOwner, IDebugNameData } from './debugName.js'; import { BugIndicatingError, DisposableStore, EqualityComparer, IDisposable, assertFn, onBugIndicatingError, strictEquals } from './commonFacade/deps.js'; -import { getLogger } from './logging.js'; +import { getLogger } from './logging/logging.js'; /** * Creates an observable that is derived from other observables. @@ -164,7 +164,7 @@ export function derivedDisposable(computeFnOr ); } -const enum DerivedState { +export const enum DerivedState { /** Initial state, no previous value, recomputation needed */ initial = 0, @@ -210,7 +210,6 @@ export class Derived extends BaseObservable im ) { super(); this.changeSummary = this.createChangeSummary?.(); - getLogger()?.handleDerivedCreated(this); } protected override onLastObserverRemoved(): void { @@ -230,7 +229,9 @@ export class Derived extends BaseObservable im } public override get(): T { - if (this._isComputing) { + const checkEnabled = false; // TODO set to true + if (this._isComputing && checkEnabled) { + // investigate why this fails in the diff editor! throw new BugIndicatingError('Cyclic deriveds are not supported yet!'); } @@ -291,7 +292,7 @@ export class Derived extends BaseObservable im let didChange = false; - this._isComputing = false; // TODO@hediet: Set to true and investigate diff editor scrolling issues! (also see test.skip('catches cyclic dependencies') + this._isComputing = true; try { const changeSummary = this.changeSummary!; @@ -312,7 +313,7 @@ export class Derived extends BaseObservable im didChange = hadValue && !(this._equalityComparator(oldValue!, this.value)); - getLogger()?.handleDerivedRecomputed(this, { + getLogger()?.handleObservableUpdated(this, { oldValue, newValue: this.value, change: undefined, @@ -399,6 +400,8 @@ export class Derived extends BaseObservable im public handleChange(observable: IObservableWithChange, change: TChange): void { if (this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { + getLogger()?.handleDerivedDependencyChanged(this, observable, change); + let shouldReact = false; try { shouldReact = this._handleChange ? this._handleChange({ @@ -460,16 +463,6 @@ export class Derived extends BaseObservable im } super.removeObserver(observer); } - - public override log(): IObservableWithChange { - if (!getLogger()) { - super.log(); - getLogger()?.handleDerivedCreated(this); - } else { - super.log(); - } - return this; - } } diff --git a/src/vs/base/common/observableInternal/index.ts b/src/vs/base/common/observableInternal/index.ts index 873cf946171c..85d4cc158f87 100644 --- a/src/vs/base/common/observableInternal/index.ts +++ b/src/vs/base/common/observableInternal/index.ts @@ -14,10 +14,10 @@ export { derivedWithCancellationToken, waitForState } from './utilsCancellation. export { constObservable, debouncedObservable, derivedConstOnceDefined, derivedObservableWithCache, derivedObservableWithWritableCache, keepObserved, latestChangedValue, mapObservableArrayCached, observableFromEvent, observableFromEventOpts, observableFromPromise, observableFromValueWithChangeEvent, observableSignal, observableSignalFromEvent, recomputeInitiallyAndOnChange, runOnChange, runOnChangeWithStore, signalFromObservable, ValueWithChangeEventFromObservable, wasEventTriggeredRecently, type IObservableSignal, } from './utils.js'; export { type DebugOwner } from './debugName.js'; -import { - ConsoleObservableLogger, - setLogger -} from './logging.js'; +import { addLogger, setLogObservableFn } from './logging/logging.js'; +import { ConsoleObservableLogger, logObservableToConsole } from './logging/consoleObservableLogger.js'; + +setLogObservableFn(logObservableToConsole); // Remove "//" in the next line to enable logging const enableLogging = false @@ -25,5 +25,5 @@ const enableLogging = false ; if (enableLogging) { - setLogger(new ConsoleObservableLogger()); + addLogger(new ConsoleObservableLogger()); } diff --git a/src/vs/base/common/observableInternal/lazyObservableValue.ts b/src/vs/base/common/observableInternal/lazyObservableValue.ts index 6c0f85aa8767..7b6697210ef4 100644 --- a/src/vs/base/common/observableInternal/lazyObservableValue.ts +++ b/src/vs/base/common/observableInternal/lazyObservableValue.ts @@ -6,7 +6,7 @@ import { EqualityComparer } from './commonFacade/deps.js'; import { BaseObservable, IObserver, ISettableObservable, ITransaction, TransactionImpl } from './base.js'; import { DebugNameData } from './debugName.js'; -import { getLogger } from './logging.js'; +import { getLogger } from './logging/logging.js'; /** * Holds off updating observers until the value is actually read. @@ -44,14 +44,14 @@ export class LazyObservableValue if (this._deltas.length > 0) { for (const change of this._deltas) { - getLogger()?.handleObservableChanged(this, { change, didChange: true, oldValue: '(unknown)', newValue: this._value, hadValue: true }); + getLogger()?.handleObservableUpdated(this, { change, didChange: true, oldValue: '(unknown)', newValue: this._value, hadValue: true }); for (const observer of this.observers) { observer.handleChange(this, change); } } this._deltas.length = 0; } else { - getLogger()?.handleObservableChanged(this, { change: undefined, didChange: true, oldValue: '(unknown)', newValue: this._value, hadValue: true }); + getLogger()?.handleObservableUpdated(this, { change: undefined, didChange: true, oldValue: '(unknown)', newValue: this._value, hadValue: true }); for (const observer of this.observers) { observer.handleChange(this, undefined); } diff --git a/src/vs/base/common/observableInternal/logging.ts b/src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts similarity index 70% rename from src/vs/base/common/observableInternal/logging.ts rename to src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts index f0bc82170d8e..325787d633d1 100644 --- a/src/vs/base/common/observableInternal/logging.ts +++ b/src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts @@ -3,55 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AutorunObserver } from './autorun.js'; -import { IObservable, TransactionImpl } from './base.js'; -import { Derived } from './derived.js'; -import { FromEventObservable } from './utils.js'; - -let globalObservableLogger: IObservableLogger | undefined; - -export function setLogger(logger: IObservableLogger): void { - globalObservableLogger = logger; -} - -export function getLogger(): IObservableLogger | undefined { - return globalObservableLogger; -} - -export function logObservable(obs: IObservable): void { - if (!globalObservableLogger) { - const l = new ConsoleObservableLogger(); - l.addFilteredObj(obs); - setLogger(l); - } else { - if (globalObservableLogger instanceof ConsoleObservableLogger) { - (globalObservableLogger as ConsoleObservableLogger).addFilteredObj(obs); - } +import { AutorunObserver } from '../autorun.js'; +import { IObservable, TransactionImpl } from '../base.js'; +import { Derived } from '../derived.js'; +import { IObservableLogger, IChangeInformation, addLogger } from './logging.js'; +import { FromEventObservable } from '../utils.js'; + +let consoleObservableLogger: ConsoleObservableLogger | undefined; + +export function logObservableToConsole(obs: IObservable): void { + if (!consoleObservableLogger) { + consoleObservableLogger = new ConsoleObservableLogger(); + addLogger(consoleObservableLogger); } -} - -interface IChangeInformation { - oldValue: unknown; - newValue: unknown; - change: unknown; - didChange: boolean; - hadValue: boolean; -} - -export interface IObservableLogger { - handleObservableChanged(observable: IObservable, info: IChangeInformation): void; - handleFromEventObservableTriggered(observable: FromEventObservable, info: IChangeInformation): void; - - handleAutorunCreated(autorun: AutorunObserver): void; - handleAutorunTriggered(autorun: AutorunObserver): void; - handleAutorunFinished(autorun: AutorunObserver): void; - - handleDerivedCreated(observable: Derived): void; - handleDerivedRecomputed(observable: Derived, info: IChangeInformation): void; - handleDerivedCleared(observable: Derived): void; - - handleBeginTransaction(transaction: TransactionImpl): void; - handleEndTransaction(): void; + consoleObservableLogger.addFilteredObj(obs); } export class ConsoleObservableLogger implements IObservableLogger { @@ -102,8 +67,45 @@ export class ConsoleObservableLogger implements IObservableLogger { : [normalText(` (unchanged)`)]; } - handleObservableChanged(observable: IObservable, info: IChangeInformation): void { + handleObservableCreated(observable: IObservable): void { + if (observable instanceof Derived) { + const derived = observable; + this.changedObservablesSets.set(derived, new Set()); + + const debugTrackUpdating = false; + if (debugTrackUpdating) { + const updating: IObservable[] = []; + (derived as any).__debugUpdating = updating; + + const existingBeginUpdate = derived.beginUpdate; + derived.beginUpdate = (obs) => { + updating.push(obs); + return existingBeginUpdate.apply(derived, [obs]); + }; + + const existingEndUpdate = derived.endUpdate; + derived.endUpdate = (obs) => { + const idx = updating.indexOf(obs); + if (idx === -1) { + console.error('endUpdate called without beginUpdate', derived.debugName, obs.debugName); + } + updating.splice(idx, 1); + return existingEndUpdate.apply(derived, [obs]); + }; + } + } + } + + handleOnListenerCountChanged(observable: IObservable, newCount: number): void { + } + + handleObservableUpdated(observable: IObservable, info: IChangeInformation): void { if (!this._isIncluded(observable)) { return; } + if (observable instanceof Derived) { + this._handleDerivedRecomputed(observable, info); + return; + } + console.log(...this.textToConsoleArgs([ formatKind('observable value changed'), styled(observable.debugName, { color: 'BlueViolet' }), @@ -125,38 +127,13 @@ export class ConsoleObservableLogger implements IObservableLogger { ); } - handleDerivedCreated(derived: Derived): void { - const existingHandleChange = derived.handleChange; - this.changedObservablesSets.set(derived, new Set()); - derived.handleChange = (observable, change) => { - this.changedObservablesSets.get(derived)!.add(observable); - return existingHandleChange.apply(derived, [observable, change]); - }; - - const debugTrackUpdating = false; - if (debugTrackUpdating) { - const updating: IObservable[] = []; - (derived as any).__debugUpdating = updating; - - const existingBeginUpdate = derived.beginUpdate; - derived.beginUpdate = (obs) => { - updating.push(obs); - return existingBeginUpdate.apply(derived, [obs]); - }; - - const existingEndUpdate = derived.endUpdate; - derived.endUpdate = (obs) => { - const idx = updating.indexOf(obs); - if (idx === -1) { - console.error('endUpdate called without beginUpdate', derived.debugName, obs.debugName); - } - updating.splice(idx, 1); - return existingEndUpdate.apply(derived, [obs]); - }; - } + handleDerivedDependencyChanged(derived: Derived, observable: IObservable, change: unknown): void { + if (!this._isIncluded(derived)) { return; } + + this.changedObservablesSets.get(derived)?.add(observable); } - handleDerivedRecomputed(derived: Derived, info: IChangeInformation): void { + _handleDerivedRecomputed(derived: Derived, info: IChangeInformation): void { if (!this._isIncluded(derived)) { return; } const changedObservables = this.changedObservablesSets.get(derived); @@ -194,15 +171,19 @@ export class ConsoleObservableLogger implements IObservableLogger { handleAutorunCreated(autorun: AutorunObserver): void { if (!this._isIncluded(autorun)) { return; } - const existingHandleChange = autorun.handleChange; this.changedObservablesSets.set(autorun, new Set()); - autorun.handleChange = (observable, change) => { - this.changedObservablesSets.get(autorun)!.add(observable); - return existingHandleChange.apply(autorun, [observable, change]); - }; } - handleAutorunTriggered(autorun: AutorunObserver): void { + handleAutorunDisposed(autorun: AutorunObserver): void { + } + + handleAutorunDependencyChanged(autorun: AutorunObserver, observable: IObservable, change: unknown): void { + if (!this._isIncluded(autorun)) { return; } + + this.changedObservablesSets.get(autorun)!.add(observable); + } + + handleAutorunStarted(autorun: AutorunObserver): void { const changedObservables = this.changedObservablesSets.get(autorun); if (!changedObservables) { return; } @@ -241,12 +222,9 @@ export class ConsoleObservableLogger implements IObservableLogger { this.indentation--; } } - -type ConsoleText = - | (ConsoleText | undefined)[] - | { text: string; style: string; data?: unknown[] } - | { data: unknown[] }; - +type ConsoleText = (ConsoleText | undefined)[] | +{ text: string; style: string; data?: unknown[] } | +{ data: unknown[] }; function consoleTextToArgs(text: ConsoleText): unknown[] { const styles = new Array(); const data: unknown[] = []; @@ -276,15 +254,12 @@ function consoleTextToArgs(text: ConsoleText): unknown[] { result.push(...data); return result; } - function normalText(text: string): ConsoleText { return styled(text, { color: 'black' }); } - function formatKind(kind: string): ConsoleText { return styled(padStr(`${kind}: `, 10), { color: 'black', bold: true }); } - function styled( text: string, options: { color: string; strikeThrough?: boolean; bold?: boolean } = { @@ -316,7 +291,7 @@ function styled( }; } -function formatValue(value: unknown, availableLen: number): string { +export function formatValue(value: unknown, availableLen: number): string { switch (typeof value) { case 'number': return '' + value; @@ -346,7 +321,6 @@ function formatValue(value: unknown, availableLen: number): string { return '' + value; } } - function formatArray(value: unknown[], availableLen: number): string { let result = '[ '; let first = true; @@ -364,7 +338,6 @@ function formatArray(value: unknown[], availableLen: number): string { result += ' ]'; return result; } - function formatObject(value: object, availableLen: number): string { if (typeof value.toString === 'function' && value.toString !== Object.prototype.toString) { const val = value.toString(); @@ -390,7 +363,6 @@ function formatObject(value: object, availableLen: number): string { result += ' }'; return result; } - function repeat(str: string, count: number): string { let result = ''; for (let i = 1; i <= count; i++) { @@ -398,7 +370,6 @@ function repeat(str: string, count: number): string { } return result; } - function padStr(str: string, length: number): string { while (str.length < length) { str += ' '; diff --git a/src/vs/base/common/observableInternal/logging/logging.ts b/src/vs/base/common/observableInternal/logging/logging.ts new file mode 100644 index 000000000000..dd6ba48416dc --- /dev/null +++ b/src/vs/base/common/observableInternal/logging/logging.ts @@ -0,0 +1,129 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AutorunObserver } from '../autorun.js'; +import { IObservable, TransactionImpl } from '../base.js'; +import type { Derived } from '../derived.js'; + +let globalObservableLogger: IObservableLogger | undefined; + +export function addLogger(logger: IObservableLogger): void { + if (!globalObservableLogger) { + globalObservableLogger = logger; + } else if (globalObservableLogger instanceof ComposedLogger) { + globalObservableLogger.loggers.push(logger); + } else { + globalObservableLogger = new ComposedLogger([globalObservableLogger, logger]); + } +} + +export function getLogger(): IObservableLogger | undefined { + return globalObservableLogger; +} + +let globalObservableLoggerFn: ((obs: IObservable) => void) | undefined = undefined; +export function setLogObservableFn(fn: (obs: IObservable) => void): void { + globalObservableLoggerFn = fn; +} + +export function logObservable(obs: IObservable): void { + if (globalObservableLoggerFn) { + globalObservableLoggerFn(obs); + } +} + +export interface IChangeInformation { + oldValue: unknown; + newValue: unknown; + change: unknown; + didChange: boolean; + hadValue: boolean; +} + +export interface IObservableLogger { + handleObservableCreated(observable: IObservable): void; + handleOnListenerCountChanged(observable: IObservable, newCount: number): void; + + handleObservableUpdated(observable: IObservable, info: IChangeInformation): void; + + handleAutorunCreated(autorun: AutorunObserver): void; + handleAutorunDisposed(autorun: AutorunObserver): void; + handleAutorunDependencyChanged(autorun: AutorunObserver, observable: IObservable, change: unknown): void; + handleAutorunStarted(autorun: AutorunObserver): void; + handleAutorunFinished(autorun: AutorunObserver): void; + + handleDerivedDependencyChanged(derived: Derived, observable: IObservable, change: unknown): void; + handleDerivedCleared(observable: Derived): void; + + handleBeginTransaction(transaction: TransactionImpl): void; + handleEndTransaction(transaction: TransactionImpl): void; +} + +class ComposedLogger implements IObservableLogger { + constructor( + public readonly loggers: IObservableLogger[], + ) { } + + handleObservableCreated(observable: IObservable): void { + for (const logger of this.loggers) { + logger.handleObservableCreated(observable); + } + } + handleOnListenerCountChanged(observable: IObservable, newCount: number): void { + for (const logger of this.loggers) { + logger.handleOnListenerCountChanged(observable, newCount); + } + } + handleObservableUpdated(observable: IObservable, info: IChangeInformation): void { + for (const logger of this.loggers) { + logger.handleObservableUpdated(observable, info); + } + } + handleAutorunCreated(autorun: AutorunObserver): void { + for (const logger of this.loggers) { + logger.handleAutorunCreated(autorun); + } + } + handleAutorunDisposed(autorun: AutorunObserver): void { + for (const logger of this.loggers) { + logger.handleAutorunDisposed(autorun); + } + } + handleAutorunDependencyChanged(autorun: AutorunObserver, observable: IObservable, change: unknown): void { + for (const logger of this.loggers) { + logger.handleAutorunDependencyChanged(autorun, observable, change); + } + } + handleAutorunStarted(autorun: AutorunObserver): void { + for (const logger of this.loggers) { + logger.handleAutorunStarted(autorun); + } + } + handleAutorunFinished(autorun: AutorunObserver): void { + for (const logger of this.loggers) { + logger.handleAutorunFinished(autorun); + } + } + handleDerivedDependencyChanged(derived: Derived, observable: IObservable, change: unknown): void { + for (const logger of this.loggers) { + logger.handleDerivedDependencyChanged(derived, observable, change); + } + } + handleDerivedCleared(observable: Derived): void { + for (const logger of this.loggers) { + logger.handleDerivedCleared(observable); + } + } + handleBeginTransaction(transaction: TransactionImpl): void { + for (const logger of this.loggers) { + logger.handleBeginTransaction(transaction); + } + } + handleEndTransaction(transaction: TransactionImpl): void { + for (const logger of this.loggers) { + logger.handleEndTransaction(transaction); + } + } +} diff --git a/src/vs/base/common/observableInternal/utils.ts b/src/vs/base/common/observableInternal/utils.ts index 56d3e692b495..9b12679d7c8f 100644 --- a/src/vs/base/common/observableInternal/utils.ts +++ b/src/vs/base/common/observableInternal/utils.ts @@ -8,7 +8,7 @@ import { BaseObservable, ConvenientObservable, IObservable, IObservableWithChang import { DebugNameData, DebugOwner, IDebugNameData, getDebugName, } from './debugName.js'; import { BugIndicatingError, DisposableStore, EqualityComparer, Event, IDisposable, IValueWithChangeEvent, strictEquals, toDisposable } from './commonFacade/deps.js'; import { derived, derivedOpts } from './derived.js'; -import { getLogger } from './logging.js'; +import { getLogger } from './logging/logging.js'; /** * Represents an efficient observable whose value never changes. @@ -36,6 +36,10 @@ class ConstObservable extends ConvenientObservable { // NO OP } + override log(): IObservableWithChange { + return this; + } + override toString(): string { return `Const: ${this.value}`; } @@ -140,7 +144,7 @@ export class FromEventObservable extends BaseObservable { subtransaction( this._getTransaction(), (tx) => { - getLogger()?.handleFromEventObservableTriggered(this, { oldValue, newValue, change: undefined, didChange, hadValue: this.hasValue }); + getLogger()?.handleObservableUpdated(this, { oldValue, newValue, change: undefined, didChange, hadValue: this.hasValue }); for (const o of this.observers) { tx.updateObserver(o, this); @@ -157,7 +161,7 @@ export class FromEventObservable extends BaseObservable { } if (!didRunTransaction) { - getLogger()?.handleFromEventObservableTriggered(this, { oldValue, newValue, change: undefined, didChange, hadValue: this.hasValue }); + getLogger()?.handleObservableUpdated(this, { oldValue, newValue, change: undefined, didChange, hadValue: this.hasValue }); } }; From e89638c11854e7ee6854156ac231b7f37b97d61c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 2 Feb 2025 11:51:22 -0800 Subject: [PATCH 0291/2632] Add new agent setting and experiment (#239439) Add new agent setting and experiment (#239277) * Contribute agent setting from vscode, with experiment to control visibility * Check agent experiment when submitting request, too * log * Fix race in test * Add separate context key mirroring the experiment, so the picker can be hidden even when the user set the setting --- .../contrib/chat/browser/chat.contribution.ts | 64 ++++++++++++++++++- .../contrib/chat/common/chatContextKeys.ts | 1 + .../contrib/chat/common/chatServiceImpl.ts | 14 +++- .../test/browser/inlineChatController.test.ts | 3 +- 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 269b6536eab8..f570cd3641de 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -13,13 +13,13 @@ import { registerEditorFeature } from '../../../../editor/common/editorFeatures. import * as nls from '../../../../nls.js'; import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; -import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; +import { Extensions as ConfigurationExtensions, ConfigurationScope, IConfigurationNode, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { Registry } from '../../../../platform/registry/common/platform.js'; import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js'; -import { WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; +import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; import { EditorExtensions, IEditorFactoryRegistry } from '../../../common/editor.js'; import { IEditorResolverService, RegisteredEditorPriority } from '../../../services/editor/common/editorResolverService.js'; import { ChatAgentLocation, ChatAgentNameService, ChatAgentService, IChatAgentNameService, IChatAgentService } from '../common/chatAgents.js'; @@ -84,6 +84,10 @@ import { ChatEditorOverlayController } from './chatEditorOverlay.js'; import '../common/promptSyntax/languageFeatures/promptLinkProvider.js'; import { PromptFilesConfig } from '../common/promptSyntax/config.js'; import { BuiltinToolsContribution } from '../common/tools/tools.js'; +import { IWorkbenchAssignmentService } from '../../../services/assignment/common/assignmentService.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { ChatContextKeys } from '../common/chatContextKeys.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -212,6 +216,61 @@ class ChatResolverContribution extends Disposable { } } +class ChatAgentSettingContribution implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.chatAgentSetting'; + + private registeredNode: IConfigurationNode | undefined; + + constructor( + @IWorkbenchAssignmentService experimentService: IWorkbenchAssignmentService, + @IProductService private readonly productService: IProductService, + @IContextKeyService contextKeyService: IContextKeyService, + ) { + if (this.productService.quality !== 'stable') { + this.registerSetting(); + } + + const expDisabledKey = ChatContextKeys.Editing.agentModeDisallowed.bindTo(contextKeyService); + experimentService.getTreatment('chatAgentEnabled').then(value => { + if (value) { + this.registerSetting(); + } else if (value === false) { + this.deregisterSetting(); + expDisabledKey.set(true); + } + }); + } + + private registerSetting() { + if (this.registeredNode) { + return; + } + + this.registeredNode = { + id: 'chatAgent', + title: nls.localize('interactiveSessionConfigurationTitle', "Chat"), + type: 'object', + properties: { + 'chat.agent.enabled': { + type: 'boolean', + description: nls.localize('chat.agent.enabled.description', "Enable agent mode for {0}. When this is enabled, a dropdown appears in the {0} view to toggle agent mode.", 'Copilot Edits'), + default: this.productService.quality !== 'stable', + tags: ['experimental', 'onExp'], + }, + } + }; + configurationRegistry.registerConfiguration(this.registeredNode); + } + + private deregisterSetting() { + if (this.registeredNode) { + configurationRegistry.deregisterConfigurations([this.registeredNode]); + this.registeredNode = undefined; + } + } +} + AccessibleViewRegistry.register(new ChatResponseAccessibleView()); AccessibleViewRegistry.register(new PanelChatAccessibilityHelp()); AccessibleViewRegistry.register(new QuickChatAccessibilityHelp()); @@ -329,6 +388,7 @@ registerWorkbenchContribution2(ChatGettingStartedContribution.ID, ChatGettingSta registerWorkbenchContribution2(ChatSetupContribution.ID, ChatSetupContribution, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(ChatQuotasStatusBarEntry.ID, ChatQuotasStatusBarEntry, WorkbenchPhase.Eventually); registerWorkbenchContribution2(BuiltinToolsContribution.ID, BuiltinToolsContribution, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(ChatAgentSettingContribution.ID, ChatAgentSettingContribution, WorkbenchPhase.BlockRestore); registerChatActions(); registerChatCopyActions(); diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 7723d67602f1..4a2798b9f0eb 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -83,5 +83,6 @@ export namespace ChatContextKeys { export const Editing = { hasToolsAgent: new RawContextKey('chatHasToolsAgent', false, { type: 'boolean', description: localize('chatEditingHasToolsAgent', "True when a tools agent is registered.") }), agentMode: new RawContextKey('chatAgentMode', false, { type: 'boolean', description: localize('chatEditingAgentMode', "True when edits is in agent mode.") }), + agentModeDisallowed: new RawContextKey('chatAgentModeDisallowed', false, { type: 'boolean', description: localize('chatAgentModeDisallowed', "True when agent mode is not allowed.") }), // experiment-driven disablement }; } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 6e2741852cf6..f6f352449499 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -23,6 +23,7 @@ import { Progress } from '../../../../platform/progress/common/progress.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import { IWorkbenchAssignmentService } from '../../../services/assignment/common/assignmentService.js'; import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { ChatAgentLocation, IChatAgent, IChatAgentCommand, IChatAgentData, IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService } from './chatAgents.js'; import { ChatModel, ChatRequestModel, ChatRequestRemovalReason, IChatModel, IChatRequestModel, IChatRequestVariableData, IChatResponseModel, IExportableChatData, ISerializableChatData, ISerializableChatDataIn, ISerializableChatsData, normalizeSerializableChatData, toChatHistoryContent, updateRanges } from './chatModel.js'; @@ -138,7 +139,8 @@ export class ChatService extends Disposable implements IChatService { @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService, @IChatVariablesService private readonly chatVariablesService: IChatVariablesService, @IChatAgentService private readonly chatAgentService: IChatAgentService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService, ) { super(); @@ -687,6 +689,7 @@ export class ChatService extends Disposable implements IChatService { const agent = (detectedAgent ?? agentPart?.agent ?? defaultAgent)!; const command = detectedCommand ?? agentSlashCommandPart?.command; await this.extensionService.activateByEvent(`onChatParticipant:${agent.id}`); + await this.checkAgentAllowed(agent); // Recompute history in case the agent or command changed const history = this.getHistoryEntriesFromModel(requests, model.sessionId, location, agent.id); @@ -811,6 +814,15 @@ export class ChatService extends Disposable implements IChatService { }; } + private async checkAgentAllowed(agent: IChatAgentData): Promise { + if (agent.isToolsAgent) { + const enabled = await this.experimentService.getTreatment('chatAgentEnabled'); + if (enabled === false) { + throw new Error('Agent is currently disabled'); + } + } + } + private attachmentKindsForTelemetry(variableData: IChatRequestVariableData): string[] { // TODO this shows why attachments still have to be cleaned up somewhat return variableData.variables.map(v => { 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 d8d8ce7467ce..10d38f3cc79a 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -841,8 +841,7 @@ suite('InlineChatController', function () { const newSession = await inlineChatSessionService.createSession(editor, {}, CancellationToken.None); assertType(newSession); - await chatService.sendRequest(newSession.chatModel.sessionId, 'Existing', { location: ChatAgentLocation.Editor }); - + await (await chatService.sendRequest(newSession.chatModel.sessionId, 'Existing', { location: ChatAgentLocation.Editor }))?.responseCreatedPromise; assert.strictEqual(newSession.chatModel.requestInProgress, true); From 201b6506c9f63340c906883de5b719136397456c Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 2 Feb 2025 11:52:09 -0800 Subject: [PATCH 0292/2632] Fix showing wrong welcome view content when offline (#239438) Fix showing wrong welcome view content when offline (#239365) Only apply agent content override hack in chatwidget welcome content, not contributed welcome views Fix microsoft/vscode-copilot#12726 --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 5 ++++- .../chat/browser/viewsWelcome/chatViewWelcomeController.ts | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index d614ffcf1be8..2d4a9c8ce4b6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -630,7 +630,10 @@ export class ChatWidget extends Disposable implements IChatWidget { const welcomePart = this._register(this.instantiationService.createInstance( ChatViewWelcomePart, { ...welcomeContent, tips, }, - { location: this.location } + { + location: this.location, + isWidgetWelcomeViewContent: true + } )); dom.append(this.welcomeMessageContainer, welcomePart.element); } diff --git a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts index 9fe96562bba2..eb8eadcfa0d3 100644 --- a/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts +++ b/src/vs/workbench/contrib/chat/browser/viewsWelcome/chatViewWelcomeController.ts @@ -116,6 +116,7 @@ export interface IChatViewWelcomeContent { export interface IChatViewWelcomeRenderOptions { firstLinkToButton?: boolean; location: ChatAgentLocation; + isWidgetWelcomeViewContent?: boolean; } export class ChatViewWelcomePart extends Disposable { @@ -146,7 +147,7 @@ export class ChatViewWelcomePart extends Disposable { title.textContent = content.title; // Preview indicator - if (options?.location === ChatAgentLocation.EditingSession && typeof content.message !== 'function' && chatAgentService.toolsAgentModeEnabled) { + if (options?.location === ChatAgentLocation.EditingSession && typeof content.message !== 'function' && chatAgentService.toolsAgentModeEnabled && options.isWidgetWelcomeViewContent) { // Override welcome message for the agent. Sort of a hack, should it come from the participant? This case is different because the welcome content typically doesn't change per ChatWidget const agentMessage = localize({ key: 'agentMessage', comment: ['{Locked="["}', '{Locked="]({0})"}'] }, "Ask Copilot to edit your files in [agent mode]({0}). Copilot will automatically use multiple requests to pick files to edit, run terminal commands, and iterate on errors.\n\nCopilot is powered by AI, so mistakes are possible. Review output carefully before use.", 'https://aka.ms/vscode-copilot-agent'); content.message = new MarkdownString(agentMessage); From 47b4f5c1dd592783d83c5ae896e26ba6654fb528 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sun, 2 Feb 2025 12:58:50 -0800 Subject: [PATCH 0293/2632] Don't hardcode editFile tool name (#239440) --- src/vs/workbench/api/common/extHostLanguageModelTools.ts | 6 +++--- .../contrib/chat/browser/languageModelToolsService.ts | 3 ++- src/vs/workbench/contrib/chat/common/tools/editFileTool.ts | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/common/extHostLanguageModelTools.ts b/src/vs/workbench/api/common/extHostLanguageModelTools.ts index 294c549a0ed0..b5ee1c0dc1c7 100644 --- a/src/vs/workbench/api/common/extHostLanguageModelTools.ts +++ b/src/vs/workbench/api/common/extHostLanguageModelTools.ts @@ -16,7 +16,7 @@ import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/ex import { ExtHostLanguageModelToolsShape, IMainContext, IToolDataDto, MainContext, MainThreadLanguageModelToolsShape } from './extHost.protocol.js'; import * as typeConvert from './extHostTypeConverters.js'; import { IToolInputProcessor } from '../../contrib/chat/common/tools/tools.js'; -import { EditToolData, EditToolInputProcessor } from '../../contrib/chat/common/tools/editFileTool.js'; +import { EditToolData, EditToolId, EditToolInputProcessor } from '../../contrib/chat/common/tools/editFileTool.js'; export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape { /** A map of tools that were registered in this EH */ @@ -61,7 +61,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape throw new Error(`Invalid tool invocation token`); } - if (toolId === 'vscode_editFile' && !isProposedApiEnabled(extension, 'chatParticipantPrivate')) { + if (toolId === EditToolId && !isProposedApiEnabled(extension, 'chatParticipantPrivate')) { throw new Error(`Invalid tool: ${toolId}`); } @@ -92,7 +92,7 @@ export class ExtHostLanguageModelTools implements ExtHostLanguageModelToolsShape return Array.from(this._allTools.values()) .map(tool => typeConvert.LanguageModelToolDescription.to(tool)) .filter(tool => { - if (tool.name === 'vscode_editFile') { + if (tool.name === EditToolId) { return isProposedApiEnabled(extension, 'chatParticipantPrivate'); } diff --git a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts index 9e3d66aeca9d..02389c3fc29b 100644 --- a/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts +++ b/src/vs/workbench/contrib/chat/browser/languageModelToolsService.ts @@ -20,6 +20,7 @@ import { ChatModel } from '../common/chatModel.js'; import { ChatToolInvocation } from '../common/chatProgressTypes/chatToolInvocation.js'; import { IChatService } from '../common/chatService.js'; import { CountTokensCallback, ILanguageModelToolsService, IToolData, IToolImpl, IToolInvocation, IToolResult } from '../common/languageModelToolsService.js'; +import { EditToolId } from '../common/tools/editFileTool.js'; interface IToolEntry { data: IToolData; @@ -186,7 +187,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo const defaultMessage = localize('toolInvocationMessage', "Using {0}", `"${tool.data.displayName}"`); const invocationMessage = prepared?.invocationMessage ?? defaultMessage; - if (tool.data.id !== 'vscode_editFile') { + if (tool.data.id !== EditToolId) { toolInvocation = new ChatToolInvocation(invocationMessage, prepared?.pastTenseMessage, prepared?.tooltip, prepared?.confirmationMessages); model.acceptResponseProgress(request, toolInvocation); if (prepared?.confirmationMessages) { diff --git a/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts b/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts index 77181a7d7dc9..f8ac80fe994d 100644 --- a/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts +++ b/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts @@ -40,8 +40,9 @@ class Person { } `; +export const EditToolId = 'vscode_editFile'; export const EditToolData: IToolData = { - id: 'vscode_editFile', + id: EditToolId, tags: ['vscode_editing'], displayName: localize('chat.tools.editFile', "Edit File"), modelDescription: `Edit a file in the workspace. Use this tool once per file that needs to be modified, even if there are multiple changes for a file. Generate the "explanation" property first. ${codeInstructions}`, From 28448cedcfb0841d0948c0c1b960999b92fbc98e Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Sun, 2 Feb 2025 23:17:36 -0300 Subject: [PATCH 0294/2632] Removes debugger scripts, as they don't work with ESM anymore. (#239445) --- .vscode/launch.json | 4 - scripts/debugger-scripts-api.d.ts | 35 -- scripts/hot-reload-injected-script.js | 483 -------------------------- 3 files changed, 522 deletions(-) delete mode 100644 scripts/debugger-scripts-api.d.ts delete mode 100644 scripts/hot-reload-injected-script.js diff --git a/.vscode/launch.json b/.vscode/launch.json index 1a6be10d6c1b..afd57934304f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -275,10 +275,6 @@ "presentation": { "hidden": true, }, - // This is read by the vscode-diagnostic-tools extension - "vscode-diagnostic-tools.debuggerScripts": [ - "${workspaceFolder}/scripts/hot-reload-injected-script.js" - ] }, { "type": "node", diff --git a/scripts/debugger-scripts-api.d.ts b/scripts/debugger-scripts-api.d.ts deleted file mode 100644 index b101855f4d0e..000000000000 --- a/scripts/debugger-scripts-api.d.ts +++ /dev/null @@ -1,35 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -type RunFunction = - | ((debugSession: IDebugSession, context: Context) => IDisposable) - | ((debugSession: IDebugSession, context: Context) => Promise); - -interface IDebugSession { - name: string; - eval(expression: string): Promise; - evalJs( - bodyFn: (...args: T) => TResult, - ...args: T - ): Promise; -} - -interface Context { - vscode: typeof import('vscode'); -} - -interface IDisposable { - dispose(): void; -} - -interface HotReloadConfig { - mode?: 'patch-prototype' | undefined; -} - -interface GlobalThisAddition { - $hotReload_applyNewExports?(args: { oldExports: Record; newSrc: string; config?: HotReloadConfig }): AcceptNewExportsFn | undefined; -} - -type AcceptNewExportsFn = (newExports: Record) => boolean; diff --git a/scripts/hot-reload-injected-script.js b/scripts/hot-reload-injected-script.js deleted file mode 100644 index 431f11b6a66b..000000000000 --- a/scripts/hot-reload-injected-script.js +++ /dev/null @@ -1,483 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// @ts-check -/// -/// - -const path = require('path'); -const fsPromise = require('fs/promises'); -const parcelWatcher = require('@parcel/watcher'); - -// This file is loaded by the vscode-diagnostic-tools extension and injected into the debugger. - - -/** - * Represents a lazy evaluation container. - * @template T - * @template TArg - */ -class Lazy { - /** - * Creates a new instance of the Lazy class. - * @param {(arg: TArg) => T} _fn - The function to be lazily evaluated. - */ - constructor(_fn) { - this._fn = _fn; - this._value = undefined; - } - - /** - * Gets the lazily evaluated value. - * @param {TArg} arg - The argument passed in to the evaluation function. - * @return {T} - */ - getValue(arg) { - if (!this._value) { - this._value = this._fn(arg); - } - return this._value; - } -} - -/** - * @param {Context['vscode']} vscode - */ -function setupGlobals(vscode) { - /** @type {DisposableStore} */ - const store = globalThis['hot-reload-injected-script-disposables'] ?? (globalThis['hot-reload-injected-script-disposables'] = new DisposableStore()); - store.clear(); - - function getConfig() { - const config = vscode.workspace.getConfiguration('vscode-diagnostic-tools').get('debuggerScriptsConfig', { - 'hotReload.sources': {} - }); - if (!config['hotReload.sources']) { - config['hotReload.sources'] = {}; - } - return config; - } - - /** - * @type {Map void>>} - */ - const enabledRelativePaths = new Map(); - const api = { - /** - * @param {string} relativePath - * @param {() => void} forceReloadFn - */ - reloadFailed: (relativePath, forceReloadFn) => { - const set = enabledRelativePaths.get(relativePath) ?? new Set(); - set.add(forceReloadFn); - enabledRelativePaths.set(relativePath, set); - - update(); - }, - - /** - * @param {string} relativePath - * @returns {HotReloadConfig} - */ - getConfig: (relativePath) => { - const config = getConfig(); - return { mode: config['hotReload.sources'][relativePath] === 'patch-prototype' ? 'patch-prototype' : undefined }; - } - }; - - const item = store.add(vscode.window.createStatusBarItem(undefined, 10000)); - - function update() { - item.hide(); - const e = vscode.window.activeTextEditor; - if (!e) { return; } - - const part = e.document.fileName.replace(/\\/g, '/').replace(/\.ts/, '.js').split('/src/')[1]; - if (!part) { return; } - - const isEnabled = api.getConfig(part)?.mode === 'patch-prototype'; - - if (!enabledRelativePaths.has(part) && !isEnabled) { - return; - } - - if (!isEnabled) { - item.backgroundColor = new vscode.ThemeColor('statusBarItem.warningBackground'); - item.text = '$(sync-ignored) hot reload disabled'; - } else { - item.backgroundColor = undefined; - item.text = '$(sync) hot reload enabled'; - } - - item.command = { - command: 'vscode-diagnostic-tools.hotReload.toggle', - title: 'Toggle hot reload', - arguments: [part], - tooltip: 'Toggle hot reload' - }; - item.tooltip = 'Toggle hot reload'; - item.show(); - } - - store.add(vscode.window.onDidChangeActiveTextEditor(e => { - update(); - })); - - store.add(vscode.workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('vscode-diagnostic-tools.debuggerScriptsConfig')) { - update(); - } - })); - - update(); - - store.add(vscode.commands.registerCommand('vscode-diagnostic-tools.hotReload.toggle', async (relativePath) => { - let config = getConfig(); - const current = config['hotReload.sources'][relativePath]; - const newValue = current === 'patch-prototype' ? undefined : 'patch-prototype'; - config = { ...config, 'hotReload.sources': { ...config['hotReload.sources'], [relativePath]: newValue } }; - - await vscode.workspace.getConfiguration('vscode-diagnostic-tools').update('debuggerScriptsConfig', config, vscode.ConfigurationTarget.Global); - - if (newValue === 'patch-prototype') { - const reloadFns = enabledRelativePaths.get(relativePath); - console.log(reloadFns); - if (reloadFns) { - for (const fn of reloadFns) { - fn(); - } - } - } - })); - - return api; -} - -const g = new Lazy(setupGlobals); - -/** @type {RunFunction} */ -module.exports.run = async function (debugSession, ctx) { - const store = new DisposableStore(); - - const global = ctx.vscode ? g.getValue(ctx.vscode) : undefined; - - const watcher = store.add(await DirWatcher.watchRecursively(path.join(__dirname, '../out/'))); - - /** - * So that the same file always gets the same reload fn. - * @type {Map void>} - */ - const reloadFns = new Map(); - - store.add(watcher.onDidChange(async changes => { - const supportedChanges = changes - .filter(c => c.path.endsWith('.js') || c.path.endsWith('.css')) - .map(c => { - const relativePath = c.path.replace(/\\/g, '/').split('/out/')[1]; - return { ...c, relativePath, config: global?.getConfig(relativePath) }; - }); - - const result = await debugSession.evalJs(function (changes, debugSessionName) { - // This function is stringified and injected into the debuggee. - - /** @type {{ count: number; originalWindowTitle: any; timeout: any; shouldReload: boolean }} */ - const hotReloadData = globalThis.$hotReloadData || (globalThis.$hotReloadData = { count: 0, messageHideTimeout: undefined, shouldReload: false }); - - /** @type {{ relativePath: string, path: string }[]} */ - const reloadFailedJsFiles = []; - - for (const change of changes) { - handleChange(change.relativePath, change.path, change.newContent, change.config); - } - - return { reloadFailedJsFiles }; - - /** - * @param {string} relativePath - * @param {string} path - * @param {string} newSrc - * @param {HotReloadConfig | undefined} config - */ - function handleChange(relativePath, path, newSrc, config) { - if (relativePath.endsWith('.css')) { - handleCssChange(relativePath); - } else if (relativePath.endsWith('.js')) { - handleJsChange(relativePath, path, newSrc, config); - } - } - - /** - * @param {string} relativePath - */ - function handleCssChange(relativePath) { - if (typeof document === 'undefined') { - return; - } - - const styleSheet = (/** @type {HTMLLinkElement[]} */ ([...document.querySelectorAll(`link[rel='stylesheet']`)])) - .find(l => new URL(l.href, document.location.href).pathname.endsWith(relativePath)); - if (styleSheet) { - setMessage(`reload ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`); - console.log(debugSessionName, 'css reloaded', relativePath); - styleSheet.href = styleSheet.href.replace(/\?.*/, '') + '?' + Date.now(); - } else { - setMessage(`could not reload ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`); - console.log(debugSessionName, 'ignoring css change, as stylesheet is not loaded', relativePath); - } - } - - /** - * @param {string} relativePath - * @param {string} newSrc - * @param {HotReloadConfig | undefined} config - */ - function handleJsChange(relativePath, path, newSrc, config) { - const moduleIdStr = trimEnd(relativePath, '.js'); - - /** @type {any} */ - const requireFn = globalThis.require; - const moduleManager = requireFn.moduleManager; - if (!moduleManager) { - console.log(debugSessionName, 'ignoring js change, as moduleManager is not available', relativePath); - return; - } - - const moduleId = moduleManager._moduleIdProvider.getModuleId(moduleIdStr); - const oldModule = moduleManager._modules2[moduleId]; - - if (!oldModule) { - console.log(debugSessionName, 'ignoring js change, as module is not loaded', relativePath); - return; - } - - // Check if we can reload - const g = /** @type {GlobalThisAddition} */ (globalThis); - - // A frozen copy of the previous exports - const oldExports = Object.freeze({ ...oldModule.exports }); - const reloadFn = g.$hotReload_applyNewExports?.({ oldExports, newSrc, config }); - - if (!reloadFn) { - console.log(debugSessionName, 'ignoring js change, as module does not support hot-reload', relativePath); - hotReloadData.shouldReload = true; - - reloadFailedJsFiles.push({ relativePath, path }); - - setMessage(`hot reload not supported for ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`); - return; - } - - // Eval maintains source maps - function newScript(/* this parameter is used by newSrc */ define) { - // eslint-disable-next-line no-eval - eval(newSrc); // CodeQL [SM01632] This code is only executed during development. It is required for the hot-reload functionality. - } - - newScript(/* define */ function (deps, callback) { - // Evaluating the new code was successful. - - // Redefine the module - delete moduleManager._modules2[moduleId]; - moduleManager.defineModule(moduleIdStr, deps, callback); - const newModule = moduleManager._modules2[moduleId]; - - - // Patch the exports of the old module, so that modules using the old module get the new exports - Object.assign(oldModule.exports, newModule.exports); - // We override the exports so that future reloads still patch the initial exports. - newModule.exports = oldModule.exports; - - const successful = reloadFn(newModule.exports); - if (!successful) { - hotReloadData.shouldReload = true; - setMessage(`hot reload failed ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`); - console.log(debugSessionName, 'hot reload was not successful', relativePath); - return; - } - - console.log(debugSessionName, 'hot reloaded', moduleIdStr); - setMessage(`successfully reloaded ${formatPath(relativePath)} - ${new Date().toLocaleTimeString()}`); - }); - } - - /** - * @param {string} message - */ - function setMessage(message) { - const domElem = /** @type {HTMLDivElement | undefined} */ (document.querySelector('.titlebar-center .window-title')); - if (!domElem) { return; } - if (!hotReloadData.timeout) { - hotReloadData.originalWindowTitle = domElem.innerText; - } else { - clearTimeout(hotReloadData.timeout); - } - if (hotReloadData.shouldReload) { - message += ' (manual reload required)'; - } - - domElem.innerText = message; - hotReloadData.timeout = setTimeout(() => { - hotReloadData.timeout = undefined; - // If wanted, we can restore the previous title message - // domElem.replaceChildren(hotReloadData.originalWindowTitle); - }, 5000); - } - - /** - * @param {string} path - * @returns {string} - */ - function formatPath(path) { - const parts = path.split('/'); - parts.reverse(); - let result = parts[0]; - parts.shift(); - for (const p of parts) { - if (result.length + p.length > 40) { - break; - } - result = p + '/' + result; - if (result.length > 20) { - break; - } - } - return result; - } - - function trimEnd(str, suffix) { - if (str.endsWith(suffix)) { - return str.substring(0, str.length - suffix.length); - } - return str; - } - - }, supportedChanges, debugSession.name.substring(0, 25)); - - for (const failedFile of result.reloadFailedJsFiles) { - const reloadFn = reloadFns.get(failedFile.relativePath) ?? (() => { - console.log('force change'); - watcher.forceChange(failedFile.path); - }); - reloadFns.set(failedFile.relativePath, reloadFn); - global?.reloadFailed(failedFile.relativePath, reloadFn); - } - })); - - return store; -}; - -class DirWatcher { - /** - * - * @param {string} dir - * @returns {Promise} - */ - static async watchRecursively(dir) { - /** @type {((changes: { path: string, newContent: string }[]) => void)[]} */ - const listeners = []; - /** @type {Map } */ - const fileContents = new Map(); - /** @type {Map} */ - const changes = new Map(); - /** @type {(handler: (changes: { path: string, newContent: string }[]) => void) => IDisposable} */ - const event = (handler) => { - listeners.push(handler); - return { - dispose: () => { - const idx = listeners.indexOf(handler); - if (idx >= 0) { - listeners.splice(idx, 1); - } - } - }; - }; - const r = parcelWatcher.subscribe(dir, async (err, events) => { - for (const e of events) { - if (e.type === 'update') { - const newContent = await fsPromise.readFile(e.path, 'utf8'); - if (fileContents.get(e.path) !== newContent) { - fileContents.set(e.path, newContent); - changes.set(e.path, { path: e.path, newContent }); - } - } - } - if (changes.size > 0) { - debounce(() => { - const uniqueChanges = Array.from(changes.values()); - changes.clear(); - listeners.forEach(l => l(uniqueChanges)); - })(); - } - }); - const result = await r; - return new DirWatcher(event, () => result.unsubscribe(), path => { - const content = fileContents.get(path); - if (content !== undefined) { - listeners.forEach(l => l([{ path: path, newContent: content }])); - } - }); - } - - /** - * @param {(handler: (changes: { path: string, newContent: string }[]) => void) => IDisposable} onDidChange - * @param {() => void} unsub - * @param {(path: string) => void} forceChange - */ - constructor(onDidChange, unsub, forceChange) { - this.onDidChange = onDidChange; - this.unsub = unsub; - this.forceChange = forceChange; - } - - dispose() { - this.unsub(); - } -} - -/** - * Debounce function calls - * @param {() => void} fn - * @param {number} delay - */ -function debounce(fn, delay = 50) { - let timeoutId; - return function (...args) { - clearTimeout(timeoutId); - timeoutId = setTimeout(() => { - fn.apply(this, args); - }, delay); - }; -} - -class DisposableStore { - constructor() { - this._toDispose = new Set(); - this._isDisposed = false; - } - - - /** - * Adds an item to the collection. - * - * @template T - * @param {T} t - The item to add. - * @returns {T} The added item. - */ - add(t) { - this._toDispose.add(t); - return t; - } - dispose() { - if (this._isDisposed) { - return; - } - this._isDisposed = true; - this.clear(); - } - clear() { - this._toDispose.forEach(item => item.dispose()); - this._toDispose.clear(); - } -} From 0270155f64566b079d714afd4faa7fba753611c0 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 3 Feb 2025 06:43:28 +0100 Subject: [PATCH 0295/2632] SCM - fix action button container height (#239456) --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index a5c029c986ff..d6f9ee965e85 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -174,7 +174,7 @@ interface ActionButtonTemplate { } export class ActionButtonRenderer implements ICompressibleTreeRenderer { - static readonly DEFAULT_HEIGHT = 30; + static readonly DEFAULT_HEIGHT = 28; static readonly TEMPLATE_ID = 'actionButton'; get templateId(): string { return ActionButtonRenderer.TEMPLATE_ID; } @@ -710,9 +710,10 @@ class ListDelegate implements IListVirtualDelegate { getHeight(element: TreeElement) { if (isSCMInput(element)) { + console.log(this.inputRenderer.getHeight(element)); return this.inputRenderer.getHeight(element); } else if (isSCMActionButton(element)) { - return ActionButtonRenderer.DEFAULT_HEIGHT + 10; + return ActionButtonRenderer.DEFAULT_HEIGHT + 8; } else { return 22; } From f029eb3f18ada5a686368beff3d14ae6982cc52a Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Mon, 3 Feb 2025 13:55:30 +0800 Subject: [PATCH 0296/2632] fix: don't run participant detection when an explicit slash command is assigned (#239458) --- src/vs/workbench/contrib/chat/common/chatServiceImpl.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index f6f352449499..f7365f520e4a 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -670,12 +670,12 @@ export class ChatService extends Disposable implements IChatService { } satisfies IChatAgentRequest; }; - if (this.configurationService.getValue('chat.detectParticipant.enabled') !== false && this.chatAgentService.hasChatParticipantDetectionProviders() && !agentPart && !commandPart && enableCommandDetection) { + if (this.configurationService.getValue('chat.detectParticipant.enabled') !== false && this.chatAgentService.hasChatParticipantDetectionProviders() && !agentPart && !commandPart && !agentSlashCommandPart && enableCommandDetection) { // We have no agent or command to scope history with, pass the full history to the participant detection provider 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); + const chatAgentRequest = await prepareChatAgentRequest(defaultAgent, undefined, enableCommandDetection, undefined, false); const result = await this.chatAgentService.detectAgentOrCommand(chatAgentRequest, defaultAgentHistory, { location }, token); if (result && this.chatAgentService.getAgent(result.agent.id)?.locations?.includes(location)) { From ba2aa54fc680be451b6e0186da793068524630d4 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 3 Feb 2025 07:26:32 +0100 Subject: [PATCH 0297/2632] Git - fix post-commit command for multi-repo workspaces (#239462) --- extensions/git/src/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index f64528275d09..d17d118de1c9 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -873,7 +873,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi } if (hint instanceof ApiRepository) { - return this.openRepositories.filter(r => r.repository === hint.repository)[0]; + hint = hint.rootUri; } if (typeof hint === 'string') { From 33945ac8fa388e24acb1b7366908e4776b56f484 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Mon, 3 Feb 2025 15:11:57 +0800 Subject: [PATCH 0298/2632] fix: don't repeat custom icons in icon labels (#239464) --- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 981933151ccb..986b37920682 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -168,6 +168,10 @@ export class IconLabel extends Disposable { iconNode = existingIconNode; } iconNode.style.backgroundImage = css.asCSSUrl(options?.iconPath); + iconNode.style.backgroundRepeat = 'no-repeat'; + iconNode.style.backgroundPosition = 'center'; + iconNode.style.backgroundSize = 'contain'; + } else if (existingIconNode) { existingIconNode.remove(); } From b6843947b12cebe46d0df4a4441ec840041ce2ea Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 3 Feb 2025 10:05:42 +0100 Subject: [PATCH 0299/2632] fix esbuild warning, https://github.com/microsoft/vscode/pull/235599 (#239469) --- src/vs/workbench/api/common/extHostTypes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index a3e72727d03c..4130d79f0ab7 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1951,7 +1951,6 @@ export namespace TextEditorSelectionChangeKind { switch (s) { case 'keyboard': return TextEditorSelectionChangeKind.Keyboard; case 'mouse': return TextEditorSelectionChangeKind.Mouse; - case 'api': case TextEditorSelectionSource.PROGRAMMATIC: case TextEditorSelectionSource.JUMP: case TextEditorSelectionSource.NAVIGATION: From 8216e850729eb038d93fcdc77ce2f3af394b7050 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 3 Feb 2025 10:11:29 +0100 Subject: [PATCH 0300/2632] SCM - fix graph title actions rendering (#239471) --- src/vs/workbench/contrib/scm/browser/media/scm.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 5a6eebc287e1..c11117fde935 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -558,6 +558,8 @@ padding: 2px 4px; } +.monaco-workbench .part.sidebar > .title > .title-actions .action-label.scm-graph-repository-picker, +.monaco-workbench .part.sidebar > .title > .title-actions .action-label.scm-graph-history-item-picker, .monaco-workbench .part.auxiliarybar > .title > .title-actions .action-label.scm-graph-repository-picker, .monaco-workbench .part.auxiliarybar > .title > .title-actions .action-label.scm-graph-history-item-picker, .monaco-workbench .part.panel > .title > .title-actions .action-label.scm-graph-repository-picker, From cda13b71f48e197d8e4ac7538874cc16224fa166 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 3 Feb 2025 10:35:37 +0100 Subject: [PATCH 0301/2632] Add test to assert LanguageModelError (#239474) * add test for https://github.com/microsoft/vscode/issues/235322 * extract options/metadata --- .../src/singlefolder-tests/lm.test.ts | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts index 8ab5c5949a15..cc995c08497b 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts @@ -13,6 +13,15 @@ suite('lm', function () { let disposables: vscode.Disposable[] = []; + const testProviderOptions: vscode.ChatResponseProviderMetadata = { + name: 'test-lm', + version: '1.0.0', + family: 'test', + vendor: 'test-lm-vendor', + maxInputTokens: 100, + maxOutputTokens: 100, + }; + setup(function () { disposables = []; }); @@ -37,14 +46,7 @@ suite('lm', function () { async provideTokenCount(_text, _token) { return 1; }, - }, { - name: 'test-lm', - version: '1.0.0', - family: 'test', - vendor: 'test-lm-vendor', - maxInputTokens: 100, - maxOutputTokens: 100, - })); + }, testProviderOptions)); const models = await vscode.lm.selectChatModels({ id: 'test-lm' }); assert.strictEqual(models.length, 1); @@ -88,14 +90,7 @@ suite('lm', function () { async provideTokenCount(_text, _token) { return 1; }, - }, { - name: 'test-lm', - version: '1.0.0', - family: 'test', - vendor: 'test-lm-vendor', - maxInputTokens: 100, - maxOutputTokens: 100, - })); + }, testProviderOptions)); const models = await vscode.lm.selectChatModels({ id: 'test-lm' }); assert.strictEqual(models.length, 1); @@ -118,15 +113,8 @@ suite('lm', function () { }, async provideTokenCount(_text, _token) { return 1; - }, - }, { - name: 'test-lm', - version: '1.0.0', - family: 'test', - vendor: 'test-lm-vendor', - maxInputTokens: 100, - maxOutputTokens: 100, - })); + } + }, testProviderOptions)); const models = await vscode.lm.selectChatModels({ id: 'test-lm' }); assert.strictEqual(models.length, 1); @@ -150,4 +138,33 @@ suite('lm', function () { // assert.ok(error instanceof Error); // todo@jrieken requires one more insiders } }); + + test('LanguageModelError instance is not thrown to extensions#235322', async function () { + + disposables.push(vscode.lm.registerChatModelProvider('test-lm', { + async provideLanguageModelResponse(_messages, _options, _extensionId, _progress, _token) { + throw vscode.LanguageModelError.Blocked('You have been blocked'); + }, + async provideTokenCount(_text, _token) { + return 1; + } + }, testProviderOptions)); + + const models = await vscode.lm.selectChatModels({ id: 'test-lm' }); + assert.strictEqual(models.length, 1); + + let output = ''; + + try { + const response = await models[0].sendRequest([vscode.LanguageModelChatMessage.User('Hello')]); + assert.ok(response); + for await (const thing of response.text) { + output += thing; + } + } catch (error) { + assert.ok(error instanceof vscode.LanguageModelError); + assert.strictEqual(error.message, 'You have been blocked'); + } + assert.strictEqual(output, ''); + }); }); From 9cdf1dbadbfc6002f277dfbfc642a51063faf7a5 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 3 Feb 2025 10:36:07 +0100 Subject: [PATCH 0302/2632] Add next edit suggestions tag to settings and options (#239475) @tag:nextEditSuggestions --- src/vs/editor/common/config/editorOptions.ts | 2 ++ .../browser/view/inlineEdits/gutterIndicatorMenu.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 0155c2e67e5f..4fda0b457780 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -4315,6 +4315,7 @@ class InlineEditorSuggest extends BaseEditorOption option(createOptionArgs({ id: c.id, title: c.title, icon: Codicon.symbolEvent, commandId: c.id, commandArgs: c.arguments }))), separator() ] : []), - option(createOptionArgs({ id: 'settings', title: localize('settings', "Settings"), icon: Codicon.gear, commandId: 'workbench.action.openSettings', commandArgs: ['inlineSuggest.edits'] })), + option(createOptionArgs({ id: 'settings', title: localize('settings', "Settings"), icon: Codicon.gear, commandId: 'workbench.action.openSettings', commandArgs: ['@tag:nextEditSuggestions'] })), ]); } From a4c8965571d8be877f424bdfb5ff7b27b0549fb3 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Mon, 3 Feb 2025 10:56:37 +0100 Subject: [PATCH 0303/2632] =?UTF-8?q?SCM=20-=20=F0=9F=92=84=20remove=20deb?= =?UTF-8?q?ug=20statement=20(#239478)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index d6f9ee965e85..84bec04b859e 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -710,7 +710,6 @@ class ListDelegate implements IListVirtualDelegate { getHeight(element: TreeElement) { if (isSCMInput(element)) { - console.log(this.inputRenderer.getHeight(element)); return this.inputRenderer.getHeight(element); } else if (isSCMActionButton(element)) { return ActionButtonRenderer.DEFAULT_HEIGHT + 8; From d76dc4f688ab73c1996245c5be2566243ba50e2d Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 3 Feb 2025 01:59:21 -0800 Subject: [PATCH 0304/2632] fixes https://github.com/microsoft/vscode-copilot-release/issues/3520 (#239359) --- .../actions/browser/dropdownWithPrimaryActionViewItem.ts | 2 +- src/vs/workbench/contrib/chat/browser/chatInputPart.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts index dbc781394719..bbdc7e37240e 100644 --- a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts +++ b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts @@ -144,7 +144,7 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem { update(dropdownAction: IAction, dropdownMenuActions: IAction[], dropdownIcon?: string): void { this._dropdown.dispose(); this._dropdown = new DropdownMenuActionViewItem(dropdownAction, dropdownMenuActions, this._contextMenuProvider, { - menuAsChild: true, + menuAsChild: this._options?.menuAsChild ?? true, classNames: ['codicon', dropdownIcon || 'codicon-chevron-down'], actionRunner: this._options?.actionRunner, hoverDelegate: this._options?.hoverDelegate, diff --git a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts index a6406dff79a8..6fc6b0b821b7 100644 --- a/src/vs/workbench/contrib/chat/browser/chatInputPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatInputPart.ts @@ -815,7 +815,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge if (this.location === ChatAgentLocation.Panel || this.location === ChatAgentLocation.Editor) { if ((action.id === ChatSubmitAction.ID || action.id === CancelAction.ID) && action instanceof MenuItemAction) { const dropdownAction = this.instantiationService.createInstance(MenuItemAction, { id: 'chat.moreExecuteActions', title: localize('notebook.moreExecuteActionsLabel', "More..."), icon: Codicon.chevronDown }, undefined, undefined, undefined, undefined); - return this.instantiationService.createInstance(ChatSubmitDropdownActionItem, action, dropdownAction, options); + return this.instantiationService.createInstance(ChatSubmitDropdownActionItem, action, dropdownAction, { ...options, menuAsChild: false }); } } From 160a5beb8664c25902498226406a405d2d7c787e Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:43:12 +0100 Subject: [PATCH 0305/2632] Cleanup line replacement view implementation (#239482) line replacement view cleanup --- ...ReplacementView.ts => replacementViews.ts} | 180 +++++++++++------- .../browser/view/inlineEdits/view.ts | 16 +- 2 files changed, 117 insertions(+), 79 deletions(-) rename src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/{wordReplacementView.ts => replacementViews.ts} (86%) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/wordReplacementView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/replacementViews.ts similarity index 86% rename from src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/wordReplacementView.ts rename to src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/replacementViews.ts index 309af28f4aae..5bd2d644c1b5 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/wordReplacementView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/replacementViews.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, toDisposable } from '../../../../../../base/common/lifecycle.js'; -import { constObservable, derived, mapObservableArrayCached } from '../../../../../../base/common/observable.js'; +import { autorun, autorunDelta, constObservable, derived, mapObservableArrayCached } from '../../../../../../base/common/observable.js'; import { editorHoverStatusBarBackground } from '../../../../../../platform/theme/common/colorRegistry.js'; import { registerColor, transparent } from '../../../../../../platform/theme/common/colorUtils.js'; import { ObservableCodeEditor } from '../../../../../browser/observableCodeEditor.js'; @@ -22,12 +22,12 @@ import { getPrefixTrim, mapOutFalsy, n, rectToProps } from './utils.js'; import { localize } from '../../../../../../nls.js'; import { IInlineEditsView } from './sideBySideDiff.js'; import { Range } from '../../../../../common/core/range.js'; -import { LineRange } from '../../../../../common/core/lineRange.js'; import { InlineDecoration, InlineDecorationType } from '../../../../../common/viewModel.js'; import { IModelDecorationOptions, TrackedRangeStickiness } from '../../../../../common/model.js'; import { $ } from '../../../../../../base/browser/dom.js'; -import { observableValue } from '../../../../../../base/common/observableInternal/base.js'; +import { IObservable } from '../../../../../../base/common/observableInternal/base.js'; import { IViewZoneChangeAccessor } from '../../../../../browser/editorBrowser.js'; +import { LineRange } from '../../../../../common/core/lineRange.js'; export const transparentHoverBackground = registerColor( 'inlineEdit.wordReplacementView.background', { @@ -277,21 +277,27 @@ export class LineReplacementView extends Disposable implements IInlineEditsView stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }; - private readonly _maxPrefixTrim = getPrefixTrim(this._replacements.flatMap(r => [r.originalRange, r.modifiedRange]), this._originalRange, this._modifiedLines, this._editor.editor); + private readonly _maxPrefixTrim = this._edit.map(e => e ? getPrefixTrim(e.replacements.flatMap(r => [r.originalRange, r.modifiedRange]), e.originalRange, e.modifiedLines, this._editor.editor) : undefined); private readonly _modifiedLineElements = derived(reader => { const lines = []; let requiredWidth = 0; - const maxPrefixTrim = this._maxPrefixTrim.prefixTrim; - const modifiedBubbles = rangesToBubbleRanges(this._replacements.map(r => r.modifiedRange)).map(r => new Range(r.startLineNumber, r.startColumn - maxPrefixTrim, r.endLineNumber, r.endColumn - maxPrefixTrim)); + const prefixTrim = this._maxPrefixTrim.read(reader); + const edit = this._edit.read(reader); + if (!edit || !prefixTrim) { + return undefined; + } + + const maxPrefixTrim = prefixTrim.prefixTrim; + const modifiedBubbles = rangesToBubbleRanges(edit.replacements.map(r => r.modifiedRange)).map(r => new Range(r.startLineNumber, r.startColumn - maxPrefixTrim, r.endLineNumber, r.endColumn - maxPrefixTrim)); const textModel = this._editor.model.get()!; - const startLineNumber = this._modifiedRange.startLineNumber; - for (let i = 0; i < this._modifiedRange.length; i++) { + const startLineNumber = edit.modifiedRange.startLineNumber; + for (let i = 0; i < edit.modifiedRange.length; i++) { const line = document.createElement('div'); const lineNumber = startLineNumber + i; - const modLine = this._modifiedLines[i].replace(/\t\n/g, '').slice(maxPrefixTrim); + const modLine = edit.modifiedLines[i].slice(maxPrefixTrim); const t = textModel.tokenization.tokenizeLinesAt(lineNumber, [modLine])?.[0]; let tokens: LineTokens; @@ -301,6 +307,7 @@ export class LineReplacementView extends Disposable implements IInlineEditsView tokens = LineTokens.createEmpty(modLine, this._languageService.languageIdCodec); } + // Inline decorations are broken down into individual spans. To be able to render rounded corners, we need to set the start and end decorations separately. const decorations = []; for (const modified of modifiedBubbles.filter(b => b.startLineNumber === lineNumber)) { const validatedEndColumn = Math.min(modified.endColumn, modLine.length + 1); @@ -309,7 +316,9 @@ export class LineReplacementView extends Disposable implements IInlineEditsView decorations.push(new InlineDecoration(new Range(1, validatedEndColumn - 1, 1, validatedEndColumn), 'end', InlineDecorationType.Regular)); } + // TODO: All lines should be rendered at once for one dom element const result = renderLines(new LineSource([tokens]), RenderOptions.fromEditor(this._editor.editor).withSetWidth(false), decorations, line, true); + this._editor.getOption(EditorOption.fontInfo).read(reader); // update when font info changes requiredWidth = Math.max(requiredWidth, result.minWidthInPx); @@ -319,10 +328,17 @@ export class LineReplacementView extends Disposable implements IInlineEditsView return { lines, requiredWidth: requiredWidth - 10 }; // TODO: Width is always too large, why? }); - private readonly _viewZoneInfo = observableValue<{ height: number; lineNumber: number } | undefined>('viewZoneInfo', undefined); private readonly _layout = derived(this, reader => { - const { requiredWidth } = this._modifiedLineElements.read(reader); + const modifiedLines = this._modifiedLineElements.read(reader); + const maxPrefixTrim = this._maxPrefixTrim.read(reader); + const edit = this._edit.read(reader); + if (!modifiedLines || !maxPrefixTrim || !edit) { + return undefined; + } + + const { prefixLeftOffset } = maxPrefixTrim; + const { requiredWidth } = modifiedLines; const lineHeight = this._editor.getOption(EditorOption.lineHeight).read(reader); const contentLeft = this._editor.layoutInfoContentLeft.read(reader); @@ -332,13 +348,12 @@ export class LineReplacementView extends Disposable implements IInlineEditsView const PADDING = 4; const textModel = this._editor.editor.getModel()!; - const { prefixLeftOffset } = this._maxPrefixTrim; - const originalLineWidths = this._originalRange.mapToLineArray(line => this._editor.editor.getOffsetForColumn(line, textModel.getLineMaxColumn(line)) - prefixLeftOffset); + const originalLineWidths = edit.originalRange.mapToLineArray(line => this._editor.editor.getOffsetForColumn(line, textModel.getLineMaxColumn(line)) - prefixLeftOffset); const maxLineWidth = Math.max(...originalLineWidths, requiredWidth); - const startLineNumber = this._originalRange.startLineNumber; - const endLineNumber = this._originalRange.endLineNumberExclusive - 1; + const startLineNumber = edit.originalRange.startLineNumber; + const endLineNumber = edit.originalRange.endLineNumberExclusive - 1; const topOfOriginalLines = this._editor.editor.getTopForLineNumber(startLineNumber) - scrollTop; const bottomOfOriginalLines = this._editor.editor.getBottomForLineNumber(endLineNumber) - scrollTop; @@ -353,26 +368,13 @@ export class LineReplacementView extends Disposable implements IInlineEditsView originalLinesOverlay.left, originalLinesOverlay.bottom + PADDING, originalLinesOverlay.width, - this._modifiedRange.length * lineHeight + edit.modifiedRange.length * lineHeight ); const background = Rect.hull([originalLinesOverlay, modifiedLinesOverlay]).withMargin(PADDING); const lowerBackground = background.intersectVertical(new OffsetRange(originalLinesOverlay.bottom, Number.MAX_SAFE_INTEGER)); const lowerText = new Rect(lowerBackground.left + PADDING, lowerBackground.top + PADDING, lowerBackground.right, lowerBackground.bottom); - // Add ViewZone if needed - const shouldShowViewZone = this._editor.editor.getOption(EditorOption.inlineSuggest).edits.codeShifting; - if (shouldShowViewZone) { - const viewZoneHeight = lowerBackground.height + 2 * PADDING; - const viewZoneLineNumber = this._originalRange.endLineNumberExclusive; - const activeViewZone = this._viewZoneInfo.get(); - if (!activeViewZone || activeViewZone.lineNumber !== viewZoneLineNumber || activeViewZone.height !== viewZoneHeight) { - this._viewZoneInfo.set({ height: viewZoneHeight, lineNumber: viewZoneLineNumber }, undefined); - } - } else if (this._viewZoneInfo.get()) { - this._viewZoneInfo.set(undefined, undefined); - } - return { originalLinesOverlay, modifiedLinesOverlay, @@ -384,52 +386,30 @@ export class LineReplacementView extends Disposable implements IInlineEditsView }; }); - private _previousViewZoneInfo: { height: number; lineNumber: number; id: string } | undefined = undefined; - protected readonly _viewZone = derived(this, reader => { - const viewZoneInfo = this._viewZoneInfo.read(reader); - this._editor.editor.changeViewZones((changeAccessor) => { - this.removePreviousViewZone(changeAccessor); - if (!viewZoneInfo) { return; } - this.addViewZone(viewZoneInfo, changeAccessor); - }); - }).recomputeInitiallyAndOnChange(this._store); - - private removePreviousViewZone(changeAccessor: IViewZoneChangeAccessor) { - if (!this._previousViewZoneInfo) { - return; - } - - changeAccessor.removeZone(this._previousViewZoneInfo.id); - - const cursorLineNumber = this._editor.cursorLineNumber.get(); - if (cursorLineNumber !== null && cursorLineNumber >= this._previousViewZoneInfo.lineNumber) { - this._editor.editor.setScrollTop(this._editor.scrollTop.get() - this._previousViewZoneInfo.height); + private readonly _viewZoneInfo = derived<{ height: number; lineNumber: number } | undefined>(reader => { + const shouldShowViewZone = this._editor.getOption(EditorOption.inlineSuggest).map(o => o.edits.codeShifting).read(reader); + if (!shouldShowViewZone) { + return undefined; } - this._previousViewZoneInfo = undefined; - } - - private addViewZone(viewZoneInfo: { height: number; lineNumber: number }, changeAccessor: IViewZoneChangeAccessor) { - const activeViewZone = changeAccessor.addZone({ - afterLineNumber: viewZoneInfo.lineNumber - 1, - heightInPx: viewZoneInfo.height, // move computation to layout? - domNode: $('div'), - }); - - const cursorLineNumber = this._editor.cursorLineNumber.get(); - if (cursorLineNumber !== null && cursorLineNumber >= viewZoneInfo.lineNumber) { - this._editor.editor.setScrollTop(this._editor.scrollTop.get() + viewZoneInfo.height); + const layout = this._layout.read(reader); + const edit = this._edit.read(reader); + if (!layout || !edit) { + return undefined; } - this._previousViewZoneInfo = { height: viewZoneInfo.height, lineNumber: viewZoneInfo.lineNumber, id: activeViewZone }; - } + const viewZoneHeight = layout.lowerBackground.height + 2 * layout.padding; + const viewZoneLineNumber = edit.originalRange.endLineNumberExclusive; + return { height: viewZoneHeight, lineNumber: viewZoneLineNumber }; + }); private readonly _div = n.div({ class: 'line-replacement', }, [ derived(reader => { const layout = mapOutFalsy(this._layout).read(reader); - if (!layout) { + const modifiedLineElements = this._modifiedLineElements.read(reader); + if (!layout || !modifiedLineElements) { return []; } @@ -445,8 +425,7 @@ export class LineReplacementView extends Disposable implements IInlineEditsView } const lineHeight = this._editor.getOption(EditorOption.lineHeight).read(reader); - const modifiedLines = this._modifiedLineElements.read(reader).lines; - modifiedLines.forEach(l => { + modifiedLineElements.lines.forEach(l => { l.style.width = `${layout.read(reader).lowerText.width}px`; l.style.height = `${lineHeight}px`; l.style.position = 'relative'; @@ -507,7 +486,7 @@ export class LineReplacementView extends Disposable implements IInlineEditsView fontWeight: this._editor.getOption(EditorOption.fontWeight), pointerEvents: 'none', } - }, [...modifiedLines]), + }, [...modifiedLineElements.lines]), n.div({ style: { position: 'absolute', @@ -532,10 +511,12 @@ export class LineReplacementView extends Disposable implements IInlineEditsView constructor( private readonly _editor: ObservableCodeEditor, - private readonly _originalRange: LineRange, - private readonly _modifiedRange: LineRange, - private readonly _modifiedLines: string[], - private readonly _replacements: readonly Replacement[], + private readonly _edit: IObservable<{ + originalRange: LineRange; + modifiedRange: LineRange; + modifiedLines: string[]; + replacements: Replacement[]; + } | undefined>, @ILanguageService private readonly _languageService: ILanguageService, ) { super(); @@ -543,8 +524,25 @@ export class LineReplacementView extends Disposable implements IInlineEditsView this._register(toDisposable(() => this._originalBubblesDecorationCollection.clear())); this._register(toDisposable(() => this._editor.editor.changeViewZones(accessor => this.removePreviousViewZone(accessor)))); - const originalBubbles = rangesToBubbleRanges(this._replacements.map(r => r.originalRange)); - this._originalBubblesDecorationCollection.set(originalBubbles.map(r => ({ range: r, options: this._originalBubblesDecorationOptions }))); + this._register(autorunDelta(this._viewZoneInfo, ({ lastValue, newValue }) => { + if (lastValue === newValue || (lastValue?.height === newValue?.height && lastValue?.lineNumber === newValue?.lineNumber)) { + return; + } + this._editor.editor.changeViewZones((changeAccessor) => { + this.removePreviousViewZone(changeAccessor); + if (!newValue) { return; } + this.addViewZone(newValue, changeAccessor); + }); + })); + + this._register(autorun(reader => { + const edit = this._edit.read(reader); + const originalBubbles = []; + if (edit) { + originalBubbles.push(...rangesToBubbleRanges(edit.replacements.map(r => r.originalRange))); + } + this._originalBubblesDecorationCollection.set(originalBubbles.map(r => ({ range: r, options: this._originalBubblesDecorationOptions }))); + })); this._register(this._editor.createOverlayWidget({ domNode: this._div.element, @@ -555,6 +553,40 @@ export class LineReplacementView extends Disposable implements IInlineEditsView allowEditorOverflow: false, })); } + + // View Zones + + private _previousViewZoneInfo: { height: number; lineNumber: number; id: string } | undefined = undefined; + + private removePreviousViewZone(changeAccessor: IViewZoneChangeAccessor) { + if (!this._previousViewZoneInfo) { + return; + } + + changeAccessor.removeZone(this._previousViewZoneInfo.id); + + const cursorLineNumber = this._editor.cursorLineNumber.get(); + if (cursorLineNumber !== null && cursorLineNumber >= this._previousViewZoneInfo.lineNumber) { + this._editor.editor.setScrollTop(this._editor.scrollTop.get() - this._previousViewZoneInfo.height); + } + + this._previousViewZoneInfo = undefined; + } + + private addViewZone(viewZoneInfo: { height: number; lineNumber: number }, changeAccessor: IViewZoneChangeAccessor) { + const activeViewZone = changeAccessor.addZone({ + afterLineNumber: viewZoneInfo.lineNumber - 1, + heightInPx: viewZoneInfo.height, // move computation to layout? + domNode: $('div'), + }); + + this._previousViewZoneInfo = { height: viewZoneInfo.height, lineNumber: viewZoneInfo.lineNumber, id: activeViewZone }; + + const cursorLineNumber = this._editor.cursorLineNumber.get(); + if (cursorLineNumber !== null && cursorLineNumber >= viewZoneInfo.lineNumber) { + this._editor.editor.setScrollTop(this._editor.scrollTop.get() + viewZoneInfo.height); + } + } } function rangesToBubbleRanges(ranges: Range[]): Range[] { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts index f512014774ac..b61c9947186b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts @@ -26,7 +26,7 @@ import { InlineEditsSideBySideDiff } from './sideBySideDiff.js'; import { applyEditToModifiedRangeMappings, createReindentEdit } from './utils.js'; import './view.css'; import { InlineEditWithChanges } from './viewAndDiffProducer.js'; -import { LineReplacementView, WordReplacementView } from './wordReplacementView.js'; +import { LineReplacementView, WordReplacementView } from './replacementViews.js'; export class InlineEditsView extends Disposable { private readonly _editorObs = observableCodeEditor(this._editor); @@ -168,9 +168,15 @@ export class InlineEditsView extends Disposable { return store.add(this._instantiationService.createInstance(WordReplacementView, this._editorObs, e, [e])); }).recomputeInitiallyAndOnChange(this._store); - protected readonly _lineReplacementView = mapObservableArrayCached(this, this._uiState.map(s => s?.state?.kind === 'lineReplacement' ? [s.state] : []), (e, store) => { // TODO: no need for map here, how can this be done with observables - return store.add(this._instantiationService.createInstance(LineReplacementView, this._editorObs, e.originalRange, e.modifiedRange, e.modifiedLines, e.replacements)); - }).recomputeInitiallyAndOnChange(this._store); + protected readonly _lineReplacementView = this._register(this._instantiationService.createInstance(LineReplacementView, + this._editorObs, + this._uiState.map(s => s?.state?.kind === 'lineReplacement' ? ({ + originalRange: s.state.originalRange, + modifiedRange: s.state.modifiedRange, + modifiedLines: s.state.modifiedLines, + replacements: s.state.replacements, + }) : undefined), + )); private readonly _useGutterIndicator = observableCodeEditor(this._editor).getOption(EditorOption.inlineSuggest).map(s => s.edits.useGutterIndicator); @@ -179,7 +185,7 @@ export class InlineEditsView extends Disposable { || this._wordReplacementViews.read(reader).some(v => v.isHovered.read(reader)) || this._deletion.isHovered.read(reader) || this._inlineDiffView.isHovered.read(reader) - || this._lineReplacementView.read(reader).some(v => v.isHovered.read(reader)); + || this._lineReplacementView.isHovered.read(reader); }); private readonly _gutterIndicatorOffset = derived(this, reader => { From d0c29a47d568c456b7852e2cab55c857a15bb1f7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 3 Feb 2025 11:52:37 +0100 Subject: [PATCH 0306/2632] Support clicking untrusted links in the dialog (fix #239193) (#239391) --- src/vs/editor/browser/services/openerService.ts | 15 +++++++++------ .../markdownRenderer/browser/markdownRenderer.ts | 7 ++++--- src/vs/platform/opener/common/opener.ts | 1 + .../parts/dialogs/dialog.web.contribution.ts | 6 ++++-- .../browser/parts/dialogs/dialogHandler.ts | 13 ++++++++++--- .../url/browser/trustedDomainsValidator.ts | 2 +- .../parts/dialogs/dialog.contribution.ts | 6 ++++-- .../common/extensionManagementService.ts | 3 +-- 8 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 870f27bdec73..64765857710f 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -73,6 +73,7 @@ class EditorOpener implements IOpener { if (typeof target === 'string') { target = URI.parse(target); } + const { selection, uri } = extractSelection(target); target = uri; @@ -169,13 +170,15 @@ export class OpenerService implements IOpenerService { } async open(target: URI | string, options?: OpenOptions): Promise { + // check with contributed validators - const targetURI = typeof target === 'string' ? URI.parse(target) : target; - // validate against the original URI that this URI resolves to, if one exists - const validationTarget = this._resolvedUriTargets.get(targetURI) ?? target; - for (const validator of this._validators) { - if (!(await validator.shouldOpen(validationTarget, options))) { - return false; + if (!options?.skipValidation) { + const targetURI = typeof target === 'string' ? URI.parse(target) : target; + const validationTarget = this._resolvedUriTargets.get(targetURI) ?? target; // validate against the original URI that this URI resolves to, if one exists + for (const validator of this._validators) { + if (!(await validator.shouldOpen(validationTarget, options))) { + return false; + } } } diff --git a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts index 4bd0ef15ad0b..d8f157876166 100644 --- a/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts +++ b/src/vs/editor/browser/widget/markdownRenderer/browser/markdownRenderer.ts @@ -60,7 +60,7 @@ export class MarkdownRenderer { }; } - protected _getRenderOptions(markdown: IMarkdownString, disposables: DisposableStore): MarkdownRenderOptions { + private _getRenderOptions(markdown: IMarkdownString, disposables: DisposableStore): MarkdownRenderOptions { return { codeBlockRenderer: async (languageAlias, value) => { // In markdown, @@ -97,7 +97,7 @@ export class MarkdownRenderer { }, actionHandler: { callback: (link) => this.openMarkdownLink(link, markdown), - disposables: disposables + disposables } }; } @@ -107,12 +107,13 @@ export class MarkdownRenderer { } } -export async function openLinkFromMarkdown(openerService: IOpenerService, link: string, isTrusted: boolean | MarkdownStringTrustedOptions | undefined): Promise { +export async function openLinkFromMarkdown(openerService: IOpenerService, link: string, isTrusted: boolean | MarkdownStringTrustedOptions | undefined, skipValidation?: boolean): Promise { try { return await openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true, allowCommands: toAllowCommandsOption(isTrusted), + skipValidation }); } catch (e) { onUnexpectedError(e); diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index eb8074255a63..813228294057 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -43,6 +43,7 @@ export type OpenExternalOptions = { readonly allowTunneling?: boolean; readonly allowContributedOpeners?: boolean | string; readonly fromWorkspace?: boolean; + readonly skipValidation?: boolean; }; export type OpenOptions = OpenInternalOptions & OpenExternalOptions; diff --git a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts index b4806127adf1..21c9440b0eb5 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialog.web.contribution.ts @@ -16,6 +16,7 @@ import { DialogService } from '../../../services/dialogs/common/dialogService.js import { Disposable } from '../../../../base/common/lifecycle.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { Lazy } from '../../../../base/common/lazy.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution { @@ -33,11 +34,12 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, @IProductService productService: IProductService, - @IClipboardService clipboardService: IClipboardService + @IClipboardService clipboardService: IClipboardService, + @IOpenerService openerService: IOpenerService ) { super(); - this.impl = new Lazy(() => new BrowserDialogHandler(logService, layoutService, keybindingService, instantiationService, productService, clipboardService)); + this.impl = new Lazy(() => new BrowserDialogHandler(logService, layoutService, keybindingService, instantiationService, productService, clipboardService, openerService)); this.model = (this.dialogService as DialogService).model; diff --git a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index 21f62b646c29..7563b30cd475 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -17,9 +17,10 @@ import { IProductService } from '../../../../platform/product/common/productServ import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { fromNow } from '../../../../base/common/date.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { MarkdownRenderer } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; +import { MarkdownRenderer, openLinkFromMarkdown } from '../../../../editor/browser/widget/markdownRenderer/browser/markdownRenderer.js'; import { defaultButtonStyles, defaultCheckboxStyles, defaultDialogStyles, defaultInputBoxStyles } from '../../../../platform/theme/browser/defaultStyles.js'; import { ResultKind } from '../../../../platform/keybinding/common/keybindingResolver.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; export class BrowserDialogHandler extends AbstractDialogHandler { @@ -40,7 +41,8 @@ export class BrowserDialogHandler extends AbstractDialogHandler { @IKeybindingService private readonly keybindingService: IKeybindingService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IProductService private readonly productService: IProductService, - @IClipboardService private readonly clipboardService: IClipboardService + @IClipboardService private readonly clipboardService: IClipboardService, + @IOpenerService private readonly openerService: IOpenerService ) { super(); } @@ -111,7 +113,12 @@ export class BrowserDialogHandler extends AbstractDialogHandler { const renderBody = customOptions ? (parent: HTMLElement) => { parent.classList.add(...(customOptions.classes || [])); customOptions.markdownDetails?.forEach(markdownDetail => { - const result = this.markdownRenderer.render(markdownDetail.markdown); + const result = this.markdownRenderer.render(markdownDetail.markdown, { + actionHandler: { + callback: link => openLinkFromMarkdown(this.openerService, link, markdownDetail.markdown.isTrusted, true /* skip URL validation to prevent another dialog from showing which is unsupported */), + disposables: dialogDisposables + } + }); parent.appendChild(result.element); result.element.classList.add(...(markdownDetail.classes || [])); dialogDisposables.add(result); diff --git a/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts b/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts index dac333694a59..3a4df27b1185 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts @@ -58,7 +58,7 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { resourceUri = resource; } - if (await this._trustedDomainService.isValid(resourceUri)) { + if (this._trustedDomainService.isValid(resourceUri)) { return true; } else { const { scheme, authority, path, query, fragment } = resourceUri; diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts index 41859ad5d3cf..305074b7fb01 100644 --- a/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts +++ b/src/vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution.ts @@ -19,6 +19,7 @@ import { DialogService } from '../../../services/dialogs/common/dialogService.js import { Disposable } from '../../../../base/common/lifecycle.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { Lazy } from '../../../../base/common/lazy.js'; +import { IOpenerService } from '../../../../platform/opener/common/opener.js'; export class DialogHandlerContribution extends Disposable implements IWorkbenchContribution { @@ -39,11 +40,12 @@ export class DialogHandlerContribution extends Disposable implements IWorkbenchC @IInstantiationService instantiationService: IInstantiationService, @IProductService productService: IProductService, @IClipboardService clipboardService: IClipboardService, - @INativeHostService nativeHostService: INativeHostService + @INativeHostService nativeHostService: INativeHostService, + @IOpenerService openerService: IOpenerService ) { super(); - this.browserImpl = new Lazy(() => new BrowserDialogHandler(logService, layoutService, keybindingService, instantiationService, productService, clipboardService)); + this.browserImpl = new Lazy(() => new BrowserDialogHandler(logService, layoutService, keybindingService, instantiationService, productService, clipboardService, openerService)); this.nativeImpl = new Lazy(() => new NativeDialogHandler(logService, nativeHostService, productService, clipboardService)); this.model = (this.dialogService as DialogService).model; diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index 33ff44eee87e..067b3a93c4fd 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -906,8 +906,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench if (verifiedPublishers.length || unverfiiedPublishers.length === 1) { for (const publisher of verifiedPublishers) { customMessage.appendText('\n'); - const publisherDomainLink = URI.parse(publisher.publisherDomain!.link); - const publisherVerifiedMessage = localize('verifiedPublisherWithName', "{0} has verified ownership of `{1}`.", getPublisherLink(publisher), `${publisherDomainLink.authority}${publisherDomainLink.path === '/' ? '' : publisherDomainLink.path}`); + const publisherVerifiedMessage = localize('verifiedPublisherWithName', "{0} has verified ownership of {1}.", getPublisherLink(publisher), `[$(link-external) ${URI.parse(publisher.publisherDomain!.link).authority}](${publisher.publisherDomain!.link})`); customMessage.appendMarkdown(`$(${verifiedPublisherIcon.id}) ${publisherVerifiedMessage}`); } if (unverfiiedPublishers.length) { From b3043483c7b09fc7b83d4225ee55cc93c3776031 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 3 Feb 2025 12:11:09 +0100 Subject: [PATCH 0307/2632] debt - more work to move aways from single editing session (#239484) * * removes unused `getOrRestoreEditingSession` * adds `getEditingSession(chatSessionId)` * lipstick * rename `currentEditingSession` to global editing session * add `EditingSessionAction` with shared code to lookup widget and corresponding editing session * use `IChatEditingService.getEditingSession` for retry --- .../chat/browser/actions/chatClearActions.ts | 12 +- .../browser/actions/chatContextActions.ts | 12 +- .../browser/actions/chatExecuteActions.ts | 6 +- .../chat/browser/actions/chatTitleActions.ts | 6 +- .../chatMarkdownContentPart.ts | 2 +- .../browser/chatEditing/chatEditingActions.ts | 113 +++++++++--------- .../chatEditing/chatEditingServiceImpl.ts | 20 ++-- .../contrib/chat/browser/chatWidget.ts | 2 +- .../browser/contrib/chatInputCompletions.ts | 2 +- .../contrib/chatInputRelatedFilesContrib.ts | 6 +- .../contrib/chat/common/chatEditingService.ts | 24 ++-- .../contrib/chat/common/tools/editFileTool.ts | 7 +- .../test/browser/inlineChatController.test.ts | 2 +- .../test/browser/inlineChatSession.test.ts | 2 +- .../chatEdit/notebookCellDecorators.ts | 2 +- .../chatEdit/notebookChatActionsOverlay.ts | 2 +- .../chatEdit/notebookChatEditController.ts | 2 +- .../contrib/chatEdit/notebookSynchronizer.ts | 2 +- .../chatEdit/notebookSynchronizerService.ts | 4 +- 19 files changed, 115 insertions(+), 113 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index 87d33948281a..04ed62ec3f73 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -137,7 +137,7 @@ export function registerNewChatActions() { * @returns false if the user had edits and did not action the dialog to take action on them, true otherwise */ private async _handleCurrentEditingSession(chatEditingService: IChatEditingService, dialogService: IDialogService): Promise { - const currentEditingSession = chatEditingService.currentEditingSessionObs.get(); + const currentEditingSession = chatEditingService.globalEditingSessionObs.get(); const currentEdits = currentEditingSession?.entries.get(); const currentEditCount = currentEdits?.length; @@ -189,7 +189,7 @@ export function registerNewChatActions() { announceChatCleared(accessibilitySignalService); const widget = widgetService.getWidgetBySessionId(context.sessionId); if (widget) { - await chatEditingService.currentEditingSessionObs.get()?.stop(true); + await chatEditingService.globalEditingSessionObs.get()?.stop(true); widget.clear(); widget.attachmentModel.clear(); widget.focusInput(); @@ -200,7 +200,7 @@ export function registerNewChatActions() { const widget = chatView.widget; announceChatCleared(accessibilitySignalService); - await chatEditingService.currentEditingSessionObs.get()?.stop(true); + await chatEditingService.globalEditingSessionObs.get()?.stop(true); widget.clear(); widget.attachmentModel.clear(); widget.focusInput(); @@ -273,7 +273,7 @@ export function registerNewChatActions() { async run(accessor: ServicesAccessor, ...args: any[]) { const chatEditingService = accessor.get(IChatEditingService); - const currentEditingSession = chatEditingService.currentEditingSession; + const currentEditingSession = chatEditingService.globalEditingSession; if (!currentEditingSession) { return; } @@ -301,11 +301,11 @@ export function registerNewChatActions() { async run(accessor: ServicesAccessor, ...args: any[]) { const chatEditingService = accessor.get(IChatEditingService); - const currentEditingSession = chatEditingService.currentEditingSession; + const currentEditingSession = chatEditingService.globalEditingSession; if (!currentEditingSession) { return; } - await chatEditingService.currentEditingSession?.redoInteraction(); + await currentEditingSession.redoInteraction(); } }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index 4123c4b8d347..f4590dbb70d5 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -496,7 +496,7 @@ export class AttachContextAction extends Action2 { } else { // file attachment if (chatEditingService) { - chatEditingService.currentEditingSessionObs.get()?.addFileToWorkingSet(pick.resource); + chatEditingService.globalEditingSessionObs.get()?.addFileToWorkingSet(pick.resource); } else { toAttach.push({ id: this._getFileContextId({ resource: pick.resource }), @@ -521,7 +521,7 @@ export class AttachContextAction extends Action2 { const uri = editor instanceof DiffEditorInput ? editor.modified.resource : editor.resource; if (uri) { if (chatEditingService) { - chatEditingService.currentEditingSessionObs.get()?.addFileToWorkingSet(uri); + chatEditingService.globalEditingSessionObs.get()?.addFileToWorkingSet(uri); } else { toAttach.push({ id: this._getFileContextId({ resource: uri }), @@ -537,7 +537,7 @@ export class AttachContextAction extends Action2 { const searchView = viewsService.getViewWithId(SEARCH_VIEW_ID) as SearchView; for (const result of searchView.model.searchResult.matches()) { if (chatEditingService) { - chatEditingService.currentEditingSessionObs.get()?.addFileToWorkingSet(result.resource); + chatEditingService.globalEditingSessionObs.get()?.addFileToWorkingSet(result.resource); } else { toAttach.push({ id: this._getFileContextId({ resource: result.resource }), @@ -576,7 +576,7 @@ export class AttachContextAction extends Action2 { }, [])); const selectedFiles = await quickInputService.pick(itemsPromise, { placeHolder: localize('relatedFiles', 'Add related files to your working set'), canPickMany: true }); for (const file of selectedFiles ?? []) { - chatEditingService?.currentEditingSessionObs.get()?.addFileToWorkingSet(file.value); + chatEditingService?.globalEditingSessionObs.get()?.addFileToWorkingSet(file.value); } } else if (isScreenshotQuickPickItem(pick)) { const blob = await hostService.getScreenshot(); @@ -779,7 +779,7 @@ export class AttachContextAction extends Action2 { }); } } else if (context.showFilesOnly) { - if (chatEditingService?.hasRelatedFilesProviders() && (widget.getInput() || chatEditingService.currentEditingSessionObs.get()?.workingSet.size)) { + if (chatEditingService?.hasRelatedFilesProviders() && (widget.getInput() || chatEditingService.globalEditingSessionObs.get()?.workingSet.size)) { quickPickItems.push({ kind: 'related-files', id: 'related-files', @@ -853,7 +853,7 @@ export class AttachContextAction extends Action2 { // Avoid attaching the same context twice const attachedContext = widget.attachmentModel.getAttachmentIDs(); if (chatEditingService) { - for (const [file, state] of chatEditingService.currentEditingSessionObs.get()?.workingSet.entries() ?? []) { + for (const [file, state] of chatEditingService.globalEditingSessionObs.get()?.workingSet.entries() ?? []) { if (state.state !== WorkingSetEntryState.Suggested) { attachedContext.add(this._getFileContextId({ resource: file })); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 8bf302ae260e..3baa6aa980ae 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -138,7 +138,7 @@ export class ToggleAgentModeAction extends Action2 { const chatService = accessor.get(IChatService); const commandService = accessor.get(ICommandService); const dialogService = accessor.get(IDialogService); - const currentEditingSession = chatEditingService.currentEditingSession; + const currentEditingSession = chatEditingService.globalEditingSession; if (!currentEditingSession) { return; } @@ -427,7 +427,7 @@ class SendToChatEditingAction extends Action2 { const dialogService = accessor.get(IDialogService); const chatEditingService = accessor.get(IChatEditingService); - const currentEditingSession = chatEditingService.currentEditingSessionObs.get(); + const currentEditingSession = chatEditingService.globalEditingSessionObs.get(); const currentEditCount = currentEditingSession?.entries.get().length; if (currentEditCount) { const result = await dialogService.confirm({ @@ -449,7 +449,7 @@ class SendToChatEditingAction extends Action2 { const { widget: editingWidget } = await viewsService.openView(EditsViewId) as ChatViewPane; for (const attachment of widget.attachmentModel.attachments) { if (attachment.isFile && URI.isUri(attachment.value)) { - chatEditingService.currentEditingSessionObs.get()?.addFileToWorkingSet(attachment.value); + chatEditingService.globalEditingSessionObs.get()?.addFileToWorkingSet(attachment.value); } else { editingWidget.attachmentModel.addContext(attachment); } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index 10dd97aff86a..3f757e90ed44 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -232,7 +232,7 @@ export function registerChatTitleActions() { const configurationService = accessor.get(IConfigurationService); const dialogService = accessor.get(IDialogService); const chatEditingService = accessor.get(IChatEditingService); - const currentEditingSession = chatEditingService.currentEditingSession; + const currentEditingSession = chatEditingService.getEditingSession(chatModel.sessionId); if (!currentEditingSession) { return; } @@ -463,9 +463,9 @@ export function registerChatTitleActions() { await viewsService.openView(EditsViewId); - let editingSession = chatEditingService.currentEditingSessionObs.get(); + let editingSession = chatEditingService.globalEditingSessionObs.get(); if (!editingSession) { - editingSession = await waitForState(chatEditingService.currentEditingSessionObs); + editingSession = await waitForState(chatEditingService.globalEditingSessionObs); } if (!editingSession) { diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts index 8d7654540df9..f00b4c0d3e93 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/chatMarkdownContentPart.ts @@ -335,7 +335,7 @@ class CollapsedCodeBlock extends Disposable { this._uri = uri; const iconText = this.labelService.getUriBasenameLabel(uri); - const modifiedEntry = this.chatEditingService.currentEditingSession?.getEntry(uri); + const modifiedEntry = this.chatEditingService.globalEditingSession?.getEntry(uri); const isComplete = !modifiedEntry?.isCurrentlyBeingModified.get(); let iconClasses: string[] = []; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index 76f056f72b0c..17573774ae7a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -17,7 +17,7 @@ import { ITextModel } from '../../../../../editor/common/model.js'; import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js'; import { ITextModelService } from '../../../../../editor/common/services/resolverService.js'; import { localize, localize2 } from '../../../../../nls.js'; -import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { Action2, IAction2Options, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; @@ -35,19 +35,41 @@ import { CHAT_CATEGORY } from '../actions/chatActions.js'; import { ChatTreeItem, IChatWidget, IChatWidgetService } from '../chat.js'; import { EditsAttachmentModel } from '../chatAttachmentModel.js'; -abstract class WorkingSetAction extends Action2 { +export abstract class EditingSessionAction extends Action2 { + + constructor(opts: Readonly) { + super({ + category: CHAT_CATEGORY, + ...opts + }); + } + run(accessor: ServicesAccessor, ...args: any[]) { const chatEditingService = accessor.get(IChatEditingService); - const currentEditingSession = chatEditingService.currentEditingSession; - if (!currentEditingSession) { + const chatWidget = accessor.get(IChatWidgetService).lastFocusedWidget; + + if (chatWidget?.location !== ChatAgentLocation.EditingSession || !chatWidget.viewModel) { return; } - const chatWidget = accessor.get(IChatWidgetService).lastFocusedWidget; - if (chatWidget?.location !== ChatAgentLocation.EditingSession) { + const chatSessionId = chatWidget.viewModel.model.sessionId; + const editingSession = chatEditingService.getEditingSession(chatSessionId); + + if (!editingSession) { return; } + return this.runEditingSessionAction(accessor, editingSession, chatWidget, ...args); + } + + abstract runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]): any; +} + + +abstract class WorkingSetAction extends EditingSessionAction { + + runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]) { + const uris: URI[] = []; if (URI.isUri(args[0])) { uris.push(args[0]); @@ -58,10 +80,10 @@ abstract class WorkingSetAction extends Action2 { return; } - return this.runWorkingSetAction(accessor, currentEditingSession, chatWidget, ...uris); + return this.runWorkingSetAction(accessor, editingSession, chatWidget, ...uris); } - abstract runWorkingSetAction(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, chatWidget: IChatWidget | undefined, ...uris: URI[]): any; + abstract runWorkingSetAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget | undefined, ...uris: URI[]): any; } registerAction2(class MarkFileAsReadonly extends WorkingSetAction { @@ -249,7 +271,7 @@ registerAction2(class DiscardAction extends WorkingSetAction { } }); -export class ChatEditingAcceptAllAction extends Action2 { +export class ChatEditingAcceptAllAction extends EditingSessionAction { constructor() { super({ @@ -280,13 +302,8 @@ export class ChatEditingAcceptAllAction extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]): Promise { - const chatEditingService = accessor.get(IChatEditingService); - const currentEditingSession = chatEditingService.currentEditingSession; - if (!currentEditingSession) { - return; - } - await currentEditingSession.accept(); + override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]) { + await editingSession.accept(); } } registerAction2(ChatEditingAcceptAllAction); @@ -331,7 +348,7 @@ registerAction2(ChatEditingDiscardAllAction); export async function discardAllEditsWithConfirmation(accessor: ServicesAccessor): Promise { const chatEditingService = accessor.get(IChatEditingService); const dialogService = accessor.get(IDialogService); - const currentEditingSession = chatEditingService.currentEditingSession; + const currentEditingSession = chatEditingService.globalEditingSession; if (!currentEditingSession) { return false; } @@ -356,7 +373,7 @@ export async function discardAllEditsWithConfirmation(accessor: ServicesAccessor return true; } -export class ChatEditingRemoveAllFilesAction extends Action2 { +export class ChatEditingRemoveAllFilesAction extends EditingSessionAction { static readonly ID = 'chatEditing.clearWorkingSet'; constructor() { @@ -377,29 +394,20 @@ export class ChatEditingRemoveAllFilesAction extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]): Promise { - const chatEditingService = accessor.get(IChatEditingService); - const currentEditingSession = chatEditingService.currentEditingSession; - if (!currentEditingSession) { - return; - } - + override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]): Promise { // Remove all files from working set - const chatWidget = accessor.get(IChatWidgetService).getWidgetBySessionId(currentEditingSession.chatSessionId); - const uris = [...currentEditingSession.workingSet.keys()]; - currentEditingSession.remove(WorkingSetEntryRemovalReason.User, ...uris); + const uris = [...editingSession.workingSet.keys()]; + editingSession.remove(WorkingSetEntryRemovalReason.User, ...uris); // Remove all file attachments - const attachmentModel = chatWidget?.attachmentModel as EditsAttachmentModel | undefined; - const fileAttachments = attachmentModel ? [...attachmentModel.excludedFileAttachments, ...attachmentModel.fileAttachments] : []; - + const fileAttachments = chatWidget.attachmentModel ? [...(chatWidget.attachmentModel as EditsAttachmentModel).excludedFileAttachments, ...(chatWidget.attachmentModel as EditsAttachmentModel).fileAttachments] : []; const attachmentIdsToRemove = fileAttachments.map(attachment => (attachment.value as URI).toString()); - chatWidget?.attachmentModel.delete(...attachmentIdsToRemove); + chatWidget.attachmentModel.delete(...attachmentIdsToRemove); } } registerAction2(ChatEditingRemoveAllFilesAction); -export class ChatEditingShowChangesAction extends Action2 { +export class ChatEditingShowChangesAction extends EditingSessionAction { static readonly ID = 'chatEditing.viewChanges'; static readonly LABEL = localize('chatEditing.viewChanges', 'View All Edits'); @@ -422,32 +430,25 @@ export class ChatEditingShowChangesAction extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]): Promise { - const chatEditingService = accessor.get(IChatEditingService); - const currentEditingSession = chatEditingService.currentEditingSession; - if (!currentEditingSession) { - return; - } - await currentEditingSession.show(); + override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]): Promise { + await editingSession.show(); } } registerAction2(ChatEditingShowChangesAction); -registerAction2(class AddFilesToWorkingSetAction extends Action2 { +registerAction2(class AddFilesToWorkingSetAction extends EditingSessionAction { constructor() { super({ id: 'workbench.action.chat.addSelectedFilesToWorkingSet', title: localize2('workbench.action.chat.addSelectedFilesToWorkingSet.label', "Add Selected Files to Working Set"), icon: Codicon.attach, - category: CHAT_CATEGORY, precondition: ChatContextKeys.location.isEqualTo(ChatAgentLocation.EditingSession), f1: true }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]): Promise { const listService = accessor.get(IListService); - const chatEditingService = accessor.get(IChatEditingService); const editorGroupService = accessor.get(IEditorGroupsService); const uris: URI[] = []; @@ -472,7 +473,7 @@ registerAction2(class AddFilesToWorkingSetAction extends Action2 { } for (const file of uris) { - chatEditingService?.currentEditingSessionObs.get()?.addFileToWorkingSet(file); + editingSession.addFileToWorkingSet(file); } } }); @@ -527,7 +528,7 @@ registerAction2(class RemoveAction extends Action2 { return; } - const session = chatEditingService.currentEditingSession; + const session = chatEditingService.globalEditingSession; if (!session) { return; } @@ -542,7 +543,7 @@ registerAction2(class RemoveAction extends Action2 { const requestsToRemove = chatRequests.slice(itemIndex); const requestIdsToRemove = new Set(requestsToRemove.map(request => request.id)); - const entriesModifiedInRequestsToRemove = chatEditingService.currentEditingSessionObs.get()?.entries.get().filter((entry) => requestIdsToRemove.has(entry.lastModifyingRequestId)) ?? []; + const entriesModifiedInRequestsToRemove = chatEditingService.globalEditingSessionObs.get()?.entries.get().filter((entry) => requestIdsToRemove.has(entry.lastModifyingRequestId)) ?? []; const shouldPrompt = entriesModifiedInRequestsToRemove.length > 0 && configurationService.getValue('chat.editing.confirmEditRequestRemoval') === true; let message: string; @@ -629,7 +630,7 @@ registerAction2(class OpenWorkingSetHistoryAction extends Action2 { } const snapshotRequestId = requests[snapshotRequestIndex]?.id; if (snapshotRequestId) { - const snapshot = chatEditingService.currentEditingSession?.getSnapshotUri(snapshotRequestId, context.uri); + const snapshot = chatEditingService.globalEditingSession?.getSnapshotUri(snapshotRequestId, context.uri); if (snapshot) { const editor = await editorService.openEditor({ resource: snapshot, label: localize('chatEditing.snapshot', '{0} (Snapshot {1})', basename(context.uri), snapshotRequestIndex - 1), options: { transient: true, activation: EditorActivation.ACTIVATE } }); if (isCodeEditor(editor)) { @@ -640,7 +641,7 @@ registerAction2(class OpenWorkingSetHistoryAction extends Action2 { } }); -registerAction2(class ResolveSymbolsContextAction extends Action2 { +registerAction2(class ResolveSymbolsContextAction extends EditingSessionAction { constructor() { super({ id: 'workbench.action.edits.addFilesFromReferences', @@ -656,15 +657,13 @@ registerAction2(class ResolveSymbolsContextAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { - const widgetService = accessor.get(IChatWidgetService); - const textModelService = accessor.get(ITextModelService); - const languageFeaturesService = accessor.get(ILanguageFeaturesService); - const [widget] = widgetService.getWidgetsByLocations(ChatAgentLocation.EditingSession); - if (!widget || args.length === 0 || !isLocation(args[0])) { + override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]): Promise { + if (args.length === 0 || !isLocation(args[0])) { return; } + const textModelService = accessor.get(ITextModelService); + const languageFeaturesService = accessor.get(ILanguageFeaturesService); const symbol = args[0] as Location; const modelReference = await textModelService.createModelReference(symbol.uri); @@ -685,10 +684,10 @@ registerAction2(class ResolveSymbolsContextAction extends Action2 { // how important it is that they make it into the working set as it has limited size const attachments = []; for (const reference of [...definitions, ...implementations, ...references]) { - attachments.push(widget.attachmentModel.asVariableEntry(reference.uri)); + attachments.push(chatWidget.attachmentModel.asVariableEntry(reference.uri)); } - widget.attachmentModel.addContext(...attachments); + chatWidget.attachmentModel.addContext(...attachments); } private async getReferences(position: Position, textModel: ITextModel, languageFeaturesService: ILanguageFeaturesService): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts index 3da10d9ae3c8..95d6d3330fa4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts @@ -64,11 +64,11 @@ export class ChatEditingService extends Disposable implements IChatEditingServic return result; }); - get currentEditingSession(): IChatEditingSession | null { + get globalEditingSession(): IChatEditingSession | null { return this._currentSessionObs.get(); } - get currentEditingSessionObs(): IObservable { + get globalEditingSessionObs(): IObservable { return this._currentSessionObs; } @@ -143,7 +143,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic const sessionIdToRestore = storageService.get(STORAGE_KEY_EDITING_SESSION, StorageScope.WORKSPACE); if (isString(sessionIdToRestore)) { if (this._chatService.getOrRestoreSession(sessionIdToRestore)) { - this._restoringEditingSession = this.startOrContinueEditingSession(sessionIdToRestore); + this._restoringEditingSession = this.startOrContinueGlobalEditingSession(sessionIdToRestore); this._restoringEditingSession.finally(() => { this._restoringEditingSession = undefined; }); @@ -154,19 +154,12 @@ export class ChatEditingService extends Disposable implements IChatEditingServic } } - async getOrRestoreEditingSession(): Promise { - if (this._restoringEditingSession) { - await this._restoringEditingSession; - } - return this.currentEditingSessionObs.get(); - } - override dispose(): void { this._currentSessionObs.get()?.dispose(); super.dispose(); } - async startOrContinueEditingSession(chatSessionId: string): Promise { + async startOrContinueGlobalEditingSession(chatSessionId: string): Promise { await this._restoringEditingSession; const session = this._currentSessionObs.get(); @@ -215,6 +208,11 @@ export class ChatEditingService extends Disposable implements IChatEditingServic return session; } + getEditingSession(chatSessionId: string): IChatEditingSession | undefined { + return this.editingSessionsObs.get() + .find(candidate => candidate.chatSessionId === chatSessionId); + } + async createAdhocEditingSession(chatSessionId: string): Promise { const session = this._instantiationService.createInstance(ChatEditingSession, chatSessionId, false, this._editingSessionFileLimitPromise, this._lookupEntry.bind(this)); await session.init(); diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 2d4a9c8ce4b6..bbce566f01ea 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -344,7 +344,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const sessionId = this._viewModel?.sessionId; if (sessionId) { if (sessionId !== currentEditSession?.chatSessionId) { - currentEditSession = await chatEditingService.startOrContinueEditingSession(sessionId); + currentEditSession = await chatEditingService.startOrContinueGlobalEditingSession(sessionId); } } else { if (currentEditSession) { diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index 68e436eeeef5..69c400a312c8 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -572,7 +572,7 @@ class BuiltinDynamicCompletions extends Disposable { const len = result.suggestions.length; // RELATED FILES - if (widget.location === ChatAgentLocation.EditingSession && widget.viewModel && this._chatEditingService.currentEditingSessionObs.get()?.chatSessionId === widget.viewModel?.sessionId) { + if (widget.location === ChatAgentLocation.EditingSession && widget.viewModel && this._chatEditingService.globalEditingSessionObs.get()?.chatSessionId === widget.viewModel?.sessionId) { const relatedFiles = (await raceTimeout(this._chatEditingService.getRelatedFiles(widget.viewModel.sessionId, widget.getInput(), token), 200)) ?? []; for (const relatedFileGroup of relatedFiles) { for (const relatedFile of relatedFileGroup.files) { diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts index 3f4045b9ce09..2389cf9a3d54 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts @@ -28,7 +28,7 @@ export class ChatRelatedFilesContribution extends Disposable implements IWorkben this._register(autorun(r => { this.chatEditingSessionDisposables.clear(); - const session = this.chatEditingService.currentEditingSessionObs.read(r); + const session = this.chatEditingService.globalEditingSessionObs.read(r); if (session) { this._handleNewEditingSession(session); } @@ -40,7 +40,7 @@ export class ChatRelatedFilesContribution extends Disposable implements IWorkben return; } - const currentEditingSession = this.chatEditingService.currentEditingSessionObs.get(); + const currentEditingSession = this.chatEditingService.globalEditingSessionObs.get(); if (!currentEditingSession) { return; } @@ -61,7 +61,7 @@ export class ChatRelatedFilesContribution extends Disposable implements IWorkben return; } - const currentEditingSession = this.chatEditingService.currentEditingSessionObs.get(); + const currentEditingSession = this.chatEditingService.globalEditingSessionObs.get(); if (!currentEditingSession || currentEditingSession.chatSessionId !== widget.viewModel?.sessionId || currentEditingSession.entries.get().length) { return; // Might have disposed while we were calculating } diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index 21560cda2cb2..3a2b40178e2b 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -24,19 +24,13 @@ export interface IChatEditingService { _serviceBrand: undefined; - readonly currentEditingSessionObs: IObservable; + readonly globalEditingSessionObs: IObservable; - readonly currentEditingSession: IChatEditingSession | null; + readonly globalEditingSession: IChatEditingSession | null; - readonly editingSessionFileLimit: number; - - startOrContinueEditingSession(chatSessionId: string): Promise; - getOrRestoreEditingSession(): Promise; + startOrContinueGlobalEditingSession(chatSessionId: string): Promise; - - hasRelatedFilesProviders(): boolean; - registerRelatedFilesProvider(handle: number, provider: IChatRelatedFilesProvider): IDisposable; - getRelatedFiles(chatSessionId: string, prompt: string, token: CancellationToken): Promise<{ group: string; files: IChatRelatedFile[] }[] | undefined>; + getEditingSession(chatSessionId: string): IChatEditingSession | undefined; /** * All editing sessions, sorted by recency, e.g the last created session comes first. @@ -47,6 +41,16 @@ export interface IChatEditingService { * Creates a new short lived editing session */ createAdhocEditingSession(chatSessionId: string): Promise; + + readonly editingSessionFileLimit: number; + + //#region related files + + hasRelatedFilesProviders(): boolean; + registerRelatedFilesProvider(handle: number, provider: IChatRelatedFilesProvider): IDisposable; + getRelatedFiles(chatSessionId: string, prompt: string, token: CancellationToken): Promise<{ group: string; files: IChatRelatedFile[] }[] | undefined>; + + //#endregion } export interface IChatRequestDraft { diff --git a/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts b/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts index f8ac80fe994d..4c758db27d31 100644 --- a/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts +++ b/src/vs/workbench/contrib/chat/common/tools/editFileTool.ts @@ -108,7 +108,8 @@ export class EditTool implements IToolImpl { content: new MarkdownString(parameters.code + '\n````\n') }); - if (this.chatEditingService.currentEditingSession?.chatSessionId !== model.sessionId) { + const editSession = this.chatEditingService.getEditingSession(model.sessionId); + if (!editSession) { throw new Error('This tool must be called from within an editing session'); } @@ -135,8 +136,8 @@ export class EditTool implements IToolImpl { let wasFileBeingModified = false; dispose = autorun((r) => { - const currentEditingSession = this.chatEditingService.currentEditingSessionObs.read(r); - const entries = currentEditingSession?.entries.read(r); + + const entries = editSession.entries.read(r); const currentFile = entries?.find((e) => e.modifiedURI.toString() === uri.toString()); if (currentFile) { if (currentFile.isCurrentlyBeingModified.read(r)) { 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 10d38f3cc79a..1e12c2c3544c 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -163,7 +163,7 @@ suite('InlineChatController', function () { [IInlineChatSessionService, new SyncDescriptor(InlineChatSessionServiceImpl)], [ICommandService, new SyncDescriptor(TestCommandService)], [IChatEditingService, new class extends mock() { - override currentEditingSessionObs: IObservable = observableValue(this, null); + override globalEditingSessionObs: IObservable = observableValue(this, null); override editingSessionsObs: IObservable = constObservable([]); }], [IEditorProgressService, new class extends mock() { 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 cfa95c164672..c7a67a79d95b 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatSession.test.ts @@ -106,7 +106,7 @@ suite('InlineChatSession', function () { } }], [IChatEditingService, new class extends mock() { - override currentEditingSessionObs: IObservable = observableValue(this, null); + override globalEditingSessionObs: IObservable = observableValue(this, null); override editingSessionsObs: IObservable = constObservable([]); }], [IChatAccessibilityService, new class extends mock() { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookCellDecorators.ts b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookCellDecorators.ts index 81a5b3776ac4..f7647dda6a75 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookCellDecorators.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookCellDecorators.ts @@ -90,7 +90,7 @@ export class NotebookCellDiffDecorator extends DisposableStore { if (!editor) { return false; } - const value = this._chatEditingService.currentEditingSessionObs.read(r); + const value = this._chatEditingService.globalEditingSessionObs.read(r); if (!value || value.state.read(r) !== ChatEditingSessionState.StreamingEdits) { return false; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts index f58810283e32..8aa7a2c4c9d7 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts @@ -33,7 +33,7 @@ export class NotebookChatActionsOverlayController extends Disposable { const notebookModel = observableFromEvent(this.notebookEditor.onDidChangeModel, e => e); this._register(autorunWithStore((r, store) => { - const session = this._chatEditingService.currentEditingSessionObs.read(r); + const session = this._chatEditingService.globalEditingSessionObs.read(r); const model = notebookModel.read(r); if (!model || !session) { return; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatEditController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatEditController.ts index 766e43a951c0..c6d9db8501f0 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatEditController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatEditController.ts @@ -80,7 +80,7 @@ class NotebookChatEditorController extends Disposable { let notebookSynchronizer: IReference; const entryObs = derived((r) => { - const session = this._chatEditingService.currentEditingSessionObs.read(r); + const session = this._chatEditingService.globalEditingSessionObs.read(r); const model = notebookModel.read(r); if (!model || !session) { return; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizer.ts b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizer.ts index 02ddf8d02ebc..2c376b3b0309 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizer.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizer.ts @@ -91,7 +91,7 @@ export class NotebookModelSynchronizer extends Disposable { super(); const entryObs = derived((r) => { - const session = _chatEditingService.currentEditingSessionObs.read(r); + const session = _chatEditingService.globalEditingSessionObs.read(r); if (!session) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizerService.ts b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizerService.ts index 9788828cf870..8f93143cccd0 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizerService.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizerService.ts @@ -28,7 +28,7 @@ class NotebookSynchronizerSaveParticipant extends NotebookSaveParticipant { } override async participate(workingCopy: IStoredFileWorkingCopy, context: IStoredFileWorkingCopySaveParticipantContext, progress: IProgress, token: CancellationToken): Promise { - const session = this._chatEditingService.currentEditingSessionObs.get(); + const session = this._chatEditingService.globalEditingSessionObs.get(); if (!session) { return; @@ -75,7 +75,7 @@ export class NotebookSynchronizerService extends Disposable implements INotebook // check if we have mirror document const resource = workingCopy.resource; - const session = this._chatEditingService.currentEditingSessionObs.get(); + const session = this._chatEditingService.globalEditingSessionObs.get(); if (session) { const entry = session.getEntry(resource); From 24851863ac83fc4d5ffe881517aa925ccfbf803f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 3 Feb 2025 12:11:23 +0100 Subject: [PATCH 0308/2632] enable GC'ed-before-disposed warning (#239485) * fix leaking link provider * enable GC'ed-before-disposed warning --- .../promptSyntax/languageFeatures/promptLinkProvider.ts | 2 +- .../performance/browser/performance.contribution.ts | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptLinkProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptLinkProvider.ts index 854272e736ed..bed3c7b3cf74 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptLinkProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptLinkProvider.ts @@ -42,7 +42,7 @@ export class PromptLinkProvider extends Disposable implements LinkProvider { ) { super(); - this.languageService.linkProvider.register(languageSelector, this); + this._register(this.languageService.linkProvider.register(languageSelector, this)); this.parserProvider = this._register(new ObjectCache(this.createParser.bind(this))); } diff --git a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts index bd69320c3584..cfe30f3c1936 100644 --- a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts +++ b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts @@ -142,17 +142,11 @@ Registry.as(Extensions.Workbench).registerWorkb // -- track leaking disposables, those that get GC'ed before having been disposed -// this is currently disabled because there is too many leaks and some false positives, e.g disposables from registers -// like MenuRegistry, CommandsRegistery etc should be marked as singleton - -const _enableLeakDetection = false - // || Boolean("true") // done "weirdly" so that a lint warning prevents you from pushing this - ; class DisposableTracking { static readonly Id = 'perf.disposableTracking'; constructor(@IEnvironmentService envService: IEnvironmentService) { - if (!envService.isBuilt && _enableLeakDetection) { + if (!envService.isBuilt && !envService.extensionTestsLocationURI) { setDisposableTracker(new GCBasedDisposableTracker()); } } From b0472538f6d09a67da0e2dcffd02ca04e456cfac Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 3 Feb 2025 12:44:58 +0100 Subject: [PATCH 0309/2632] renaming hideWidgets to hideGlyphHover in GlyphHoverController (#236089) --- .../hover/browser/glyphHoverController.ts | 18 +++++++----------- .../contrib/chat/browser/codeBlockPart.ts | 6 +++--- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/glyphHoverController.ts b/src/vs/editor/contrib/hover/browser/glyphHoverController.ts index c0d37ae82e02..9048716322a2 100644 --- a/src/vs/editor/contrib/hover/browser/glyphHoverController.ts +++ b/src/vs/editor/contrib/hover/browser/glyphHoverController.ts @@ -93,7 +93,7 @@ export class GlyphHoverController extends Disposable implements IEditorContribut this._listenersStore.add(this._editor.onMouseLeave((e) => this._onEditorMouseLeave(e))); this._listenersStore.add(this._editor.onDidChangeModel(() => { this._cancelScheduler(); - this._hideWidgets(); + this.hideGlyphHover(); })); this._listenersStore.add(this._editor.onDidChangeModelContent(() => this._cancelScheduler())); this._listenersStore.add(this._editor.onDidScrollChange((e: IScrollEvent) => this._onEditorScrollChanged(e))); @@ -110,7 +110,7 @@ export class GlyphHoverController extends Disposable implements IEditorContribut private _onEditorScrollChanged(e: IScrollEvent): void { if (e.scrollTopChanged || e.scrollLeftChanged) { - this._hideWidgets(); + this.hideGlyphHover(); } } @@ -120,7 +120,7 @@ export class GlyphHoverController extends Disposable implements IEditorContribut if (shouldNotHideCurrentHoverWidget) { return; } - this._hideWidgets(); + this.hideGlyphHover(); } private _isMouseOnGlyphHoverWidget(mouseEvent: IPartialEditorMouseEvent): boolean { @@ -148,7 +148,7 @@ export class GlyphHoverController extends Disposable implements IEditorContribut if (_sticky) { return; } - this._hideWidgets(); + this.hideGlyphHover(); } private _shouldNotRecomputeCurrentHoverWidget(mouseEvent: IEditorMouseEvent): boolean { @@ -183,7 +183,7 @@ export class GlyphHoverController extends Disposable implements IEditorContribut if (_sticky) { return; } - this._hideWidgets(); + this.hideGlyphHover(); } private _tryShowHoverWidget(mouseEvent: IEditorMouseEvent): boolean { @@ -202,10 +202,10 @@ export class GlyphHoverController extends Disposable implements IEditorContribut // Do not hide hover when a modifier key is pressed return; } - this._hideWidgets(); + this.hideGlyphHover(); } - private _hideWidgets(): void { + public hideGlyphHover(): void { if (_sticky) { return; } @@ -219,10 +219,6 @@ export class GlyphHoverController extends Disposable implements IEditorContribut return this._glyphWidget; } - public hideContentHover(): void { - this._hideWidgets(); - } - public override dispose(): void { super.dispose(); this._unhookListeners(); diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 29ed76b29c48..a4dd3ce27263 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -407,7 +407,7 @@ export class CodeBlockPart extends Disposable { private clearWidgets() { ContentHoverController.get(this.editor)?.hideContentHover(); - GlyphHoverController.get(this.editor)?.hideContentHover(); + GlyphHoverController.get(this.editor)?.hideGlyphHover(); } private async updateEditor(data: ICodeBlockData): Promise { @@ -742,8 +742,8 @@ export class CodeCompareBlockPart extends Disposable { private clearWidgets() { ContentHoverController.get(this.diffEditor.getOriginalEditor())?.hideContentHover(); ContentHoverController.get(this.diffEditor.getModifiedEditor())?.hideContentHover(); - GlyphHoverController.get(this.diffEditor.getOriginalEditor())?.hideContentHover(); - GlyphHoverController.get(this.diffEditor.getModifiedEditor())?.hideContentHover(); + GlyphHoverController.get(this.diffEditor.getOriginalEditor())?.hideGlyphHover(); + GlyphHoverController.get(this.diffEditor.getModifiedEditor())?.hideGlyphHover(); } private async updateEditor(data: ICodeCompareBlockData, token: CancellationToken): Promise { From b36ca1b25cf29bc060931320bf21fea7174a9d75 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 3 Feb 2025 12:46:33 +0100 Subject: [PATCH 0310/2632] Correctly placing folding icons in sticky scroll (#239487) adding code --- src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css | 4 ++-- .../editor/contrib/stickyScroll/browser/stickyScrollWidget.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css b/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css index ffe968fdb575..12480664119e 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScroll.css @@ -37,8 +37,8 @@ .monaco-editor .sticky-line-number .codicon-folding-collapsed { float: right; transition: var(--vscode-editorStickyScroll-foldingOpacityTransition); - display: flex; - align-items: center; + position: absolute; + margin-left: 2px; } .monaco-editor .sticky-line-content { diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts index f805b7879b1b..fada2e0c7cff 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts @@ -354,6 +354,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget { const foldingIcon = this._renderFoldingIconForLine(foldingModel, line); if (foldingIcon) { lineNumberHTMLNode.appendChild(foldingIcon.domNode); + foldingIcon.domNode.style.left = `${layoutInfo.lineNumbersWidth + layoutInfo.lineNumbersLeft}px`; } this._editor.applyFontInfo(lineHTMLNode); @@ -525,8 +526,9 @@ class StickyFoldingIcon { public dimension: number ) { this.domNode = document.createElement('div'); - this.domNode.style.width = `${dimension}px`; + this.domNode.style.width = `26px`; this.domNode.style.height = `${dimension}px`; + this.domNode.style.lineHeight = `${dimension}px`; this.domNode.className = ThemeIcon.asClassName(isCollapsed ? foldingCollapsedIcon : foldingExpandedIcon); } From beb0595efe5aba2b3a35ef3930dd62c5355ddd84 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 3 Feb 2025 02:44:17 -0800 Subject: [PATCH 0311/2632] Add test for parsing out multiple CD PATHs and files --- .../browser/terminalCompletionService.test.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts index 92d723eb8d9a..0c2ed66de936 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/test/browser/terminalCompletionService.test.ts @@ -423,5 +423,49 @@ suite('TerminalCompletionService', () => { { label: '../', detail: '/' }, ], { replacementIndex: 3, replacementLength: 0 }); }); + + test('cd | should support pulling from multiple paths in $CDPATH', async () => { + configurationService.setUserConfiguration('terminal.integrated.suggest.cdPath', 'relative'); + const pathPrefix = isWindows ? 'c:\\' : '/'; + const delimeter = isWindows ? ';' : ':'; + const separator = isWindows ? '\\' : '/'; + shellEnvDetection.setEnvironment({ CDPATH: `${pathPrefix}cdpath1_value${delimeter}${pathPrefix}cdpath2_value${separator}inner_dir` }, true); + + const uriPathPrefix = isWindows ? 'file:///c:/' : 'file:///'; + validResources = [ + URI.parse(`${uriPathPrefix}test`), + URI.parse(`${uriPathPrefix}cdpath1_value`), + URI.parse(`${uriPathPrefix}cdpath2_value`), + URI.parse(`${uriPathPrefix}cdpath2_value/inner_dir`) + ]; + childResources = [ + { resource: URI.parse(`${uriPathPrefix}cdpath1_value/folder1/`), isDirectory: true }, + { resource: URI.parse(`${uriPathPrefix}cdpath1_value/folder2/`), isDirectory: true }, + { resource: URI.parse(`${uriPathPrefix}cdpath1_value/file1.txt`), isFile: true }, + { resource: URI.parse(`${uriPathPrefix}cdpath2_value/inner_dir/folder1/`), isDirectory: true }, + { resource: URI.parse(`${uriPathPrefix}cdpath2_value/inner_dir/folder2/`), isDirectory: true }, + { resource: URI.parse(`${uriPathPrefix}cdpath2_value/inner_dir/file1.txt`), isFile: true }, + ]; + + const resourceRequestConfig: TerminalResourceRequestConfig = { + cwd: URI.parse(`${uriPathPrefix}test`), + foldersRequested: true, + filesRequested: true, + pathSeparator, + // TODO: This is a hack to make the test pass, clean up when https://github.com/microsoft/vscode/issues/239411 is done + shouldNormalizePrefix: !isWindows + }; + const result = await terminalCompletionService.resolveResources(resourceRequestConfig, 'cd ', 3, provider, capabilities); + + const finalPrefix = isWindows ? 'C:\\' : '/'; + assertCompletions(result, [ + { label: '.', detail: `${finalPrefix}test/` }, + { label: 'folder1', detail: `CDPATH ${finalPrefix}cdpath1_value/folder1/` }, + { label: 'folder2', detail: `CDPATH ${finalPrefix}cdpath1_value/folder2/` }, + { label: 'folder1', detail: `CDPATH ${finalPrefix}cdpath2_value/inner_dir/folder1/` }, + { label: 'folder2', detail: `CDPATH ${finalPrefix}cdpath2_value/inner_dir/folder2/` }, + { label: '../', detail: finalPrefix }, + ], { replacementIndex: 3, replacementLength: 0 }); + }); }); }); From ff39acb25de7a6c261e817f7c3da36bf9ebf7f79 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 3 Feb 2025 14:04:32 +0100 Subject: [PATCH 0312/2632] Edit Context: early return when edit context primary selection does not correspond to current selection (#239495) early return when edit context primary selection does not correspond to current selection --- .../browser/controller/editContext/native/nativeEditContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts index 537c23fec5fd..75acb7c2c8d7 100644 --- a/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts +++ b/src/vs/editor/browser/controller/editContext/native/nativeEditContext.ts @@ -304,7 +304,7 @@ export class NativeEditContext extends AbstractEditContext { return; } if (!this._editContextPrimarySelection.equalsSelection(this._primarySelection)) { - this._updateEditContext(); + return; } const model = this._context.viewModel.model; const startPositionOfEditContext = this._editContextStartPosition(); From 0e0b962c111745f19af8efed2a385310de2bb049 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 3 Feb 2025 06:01:57 -0800 Subject: [PATCH 0313/2632] Add tests for simple completion model and fix bad sorting issue --- .../suggest/browser/simpleCompletionModel.ts | 14 +- .../browser/simpleCompletionModel.test.ts | 199 ++++++++++++++++++ 2 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts diff --git a/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts b/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts index 4031bba7bae3..eff68db51be1 100644 --- a/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts +++ b/src/vs/workbench/services/suggest/browser/simpleCompletionModel.ts @@ -195,9 +195,14 @@ export class SimpleCompletionModel { return score; } - // Sort files with the same score against each other specially + // Sort by underscore penalty (eg. `__init__/` should be penalized) + if (a.underscorePenalty !== b.underscorePenalty) { + return a.underscorePenalty - b.underscorePenalty; + } + + // Sort files of the same name by extension const isArg = leadingLineContent.includes(' '); - if (!isArg && a.fileExtLow.length > 0 && b.fileExtLow.length > 0) { + if (!isArg && a.labelLowExcludeFileExt === b.labelLowExcludeFileExt) { // Then by label length ascending (excluding file extension if it's a file) score = a.labelLowExcludeFileExt.length - b.labelLowExcludeFileExt.length; if (score !== 0) { @@ -215,11 +220,6 @@ export class SimpleCompletionModel { } } - // Sort by underscore penalty (eg. `__init__/` should be penalized) - if (a.underscorePenalty !== b.underscorePenalty) { - return a.underscorePenalty - b.underscorePenalty; - } - // Sort by folder depth (eg. `vscode/` should come before `vscode-.../`) if (a.labelLowNormalizedPath && b.labelLowNormalizedPath) { // Directories diff --git a/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts b/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts new file mode 100644 index 000000000000..706c1fc1f30b --- /dev/null +++ b/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts @@ -0,0 +1,199 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import assert from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { LineContext, SimpleCompletionModel } from '../../browser/simpleCompletionModel.js'; +import { SimpleCompletionItem, type ISimpleCompletion } from '../../browser/simpleCompletionItem.js'; + +function createItem(options: Partial): SimpleCompletionItem { + return new SimpleCompletionItem({ + ...options, + label: options.label || 'defaultLabel', + provider: options.provider || 'defaultProvider', + replacementIndex: options.replacementIndex || 0, + replacementLength: options.replacementLength || 1, + }); +} + +function createFileItems(...labels: string[]): SimpleCompletionItem[] { + return labels.map(label => createItem({ label, isFile: true })); +} + +function createFileItemsModel(...labels: string[]): SimpleCompletionModel { + return new SimpleCompletionModel( + createFileItems(...labels), + new LineContext('', 0) + ); +} + +function createFolderItems(...labels: string[]): SimpleCompletionItem[] { + return labels.map(label => createItem({ label, isDirectory: true })); +} + +function createFolderItemsModel(...labels: string[]): SimpleCompletionModel { + return new SimpleCompletionModel( + createFolderItems(...labels), + new LineContext('', 0) + ); +} + +function assertItems(model: SimpleCompletionModel, labels: string[]): void { + assert.deepStrictEqual(model.items.map(i => i.completion.label), labels); + assert.strictEqual(model.items.length, labels.length); // sanity check +} + +suite('SimpleCompletionModel', function () { + ensureNoDisposablesAreLeakedInTestSuite(); + + let model: SimpleCompletionModel; + + test('should handle an empty list', function () { + model = new SimpleCompletionModel([], new LineContext('', 0)); + + assert.strictEqual(model.items.length, 0); + }); + + test('should handle a list with one item', function () { + model = new SimpleCompletionModel([ + createItem({ label: 'a' }), + ], new LineContext('', 0)); + + assert.strictEqual(model.items.length, 1); + assert.strictEqual(model.items[0].completion.label, 'a'); + }); + + test('should sort alphabetically', function () { + model = new SimpleCompletionModel([ + createItem({ label: 'b' }), + createItem({ label: 'z' }), + createItem({ label: 'a' }), + ], new LineContext('', 0)); + + assert.strictEqual(model.items.length, 3); + assert.strictEqual(model.items[0].completion.label, 'a'); + assert.strictEqual(model.items[1].completion.label, 'b'); + assert.strictEqual(model.items[2].completion.label, 'z'); + }); + + suite('files and folders', () => { + test('should deprioritize files that start with underscore', function () { + const initial = ['_a', 'a', 'z']; + const expected = ['a', 'z', '_a']; + assertItems(createFileItemsModel(...initial), expected); + assertItems(createFolderItemsModel(...initial), expected); + }); + + test('should ignore the dot in dotfiles when sorting', function () { + const initial = ['b', '.a', 'a', '.b']; + const expected = ['.a', 'a', 'b', '.b']; + assertItems(createFileItemsModel(...initial), expected); + assertItems(createFolderItemsModel(...initial), expected); + }); + + test('should handle many files and folders correctly', function () { + // This is VS Code's root directory with some python items added that have special + // sorting + const items = [ + ...createFolderItems( + '__pycache', + '.build', + '.configurations', + '.devcontainer', + '.eslint-plugin-local', + '.github', + '.profile-oss', + '.vscode', + '.vscode-test', + 'build', + 'cli', + 'extensions', + 'node_modules', + 'out', + 'remote', + 'resources', + 'scripts', + 'src', + 'test', + ), + ...createFileItems( + '__init__.py', + '.editorconfig', + '.eslint-ignore', + '.git-blame-ignore-revs', + '.gitattributes', + '.gitignore', + '.lsifrc.json', + '.mailmap', + '.mention-bot', + '.npmrc', + '.nvmrc', + '.vscode-test.js', + 'cglicenses.json', + 'cgmanifest.json', + 'CodeQL.yml', + 'CONTRIBUTING.md', + 'eslint.config.js', + 'gulpfile.js', + 'LICENSE.txt', + 'package-lock.json', + 'package.json', + 'product.json', + 'README.md', + 'SECURITY.md', + 'ThirdPartyNotices.txt', + 'tsfmt.json', + ) + ]; + const model = new SimpleCompletionModel(items, new LineContext('', 0)); + assertItems(model, [ + '.build', + 'build', + 'cglicenses.json', + 'cgmanifest.json', + 'cli', + 'CodeQL.yml', + '.configurations', + 'CONTRIBUTING.md', + '.devcontainer', + '.npmrc', + '.gitignore', + '.editorconfig', + 'eslint.config.js', + '.eslint-ignore', + '.eslint-plugin-local', + 'extensions', + '.gitattributes', + '.git-blame-ignore-revs', + '.github', + 'gulpfile.js', + 'LICENSE.txt', + '.lsifrc.json', + '.nvmrc', + '.mailmap', + '.mention-bot', + 'node_modules', + 'out', + 'package.json', + 'package-lock.json', + 'product.json', + '.profile-oss', + 'README.md', + 'remote', + 'resources', + 'scripts', + 'SECURITY.md', + 'src', + 'test', + 'ThirdPartyNotices.txt', + 'tsfmt.json', + '.vscode', + '.vscode-test', + '.vscode-test.js', + '__init__.py', + '__pycache', + ]); + }); + }); +}); From bb4a59dbbc307681981bab3abcf72139320c4064 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 3 Feb 2025 15:40:35 +0100 Subject: [PATCH 0314/2632] debt - fix some disposabe leaks (#239497) --- src/vs/workbench/contrib/debug/browser/breakpointsView.ts | 4 ++-- src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts | 4 ++-- src/vs/workbench/contrib/scm/browser/scmViewPane.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index bc5b100d27b4..a4da5adbe0e3 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -160,7 +160,7 @@ export class BreakpointsView extends ViewPane { this._register(this.list.onContextMenu(this.onListContextMenu, this)); - this.list.onMouseMiddleClick(async ({ element }) => { + this._register(this.list.onMouseMiddleClick(async ({ element }) => { if (element instanceof Breakpoint) { await this.debugService.removeBreakpoints(element.getId()); } else if (element instanceof FunctionBreakpoint) { @@ -170,7 +170,7 @@ export class BreakpointsView extends ViewPane { } else if (element instanceof InstructionBreakpoint) { await this.debugService.removeInstructionBreakpoints(element.instructionReference, element.offset); } - }); + })); this._register(this.list.onDidOpen(async e => { if (!e.element) { diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 84c072d12f23..6e639a98c133 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -349,11 +349,11 @@ class MarkerWidget extends Disposable { return undefined; } })); - this.disposables.add(toDisposable(() => multilineActionbar.dispose())); + this.disposables.add(multilineActionbar); const viewModel = this.markersViewModel.getViewModel(marker); const multiline = viewModel && viewModel.multiline; - const action = new Action(toggleMultilineAction); + const action = this.disposables.add(new Action(toggleMultilineAction)); action.enabled = !!viewModel && marker.lines.length > 1; action.tooltip = multiline ? localize('single line', "Show message in single line") : localize('multi line', "Show message in multiple lines"); action.class = ThemeIcon.asClassName(multiline ? expandedIcon : collapsedIcon); diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 84bec04b859e..c7c7b4c68ed6 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1457,7 +1457,7 @@ class SCMInputWidgetToolbar extends WorkbenchToolBar { this._disposables.value.add(input.repository.provider.onDidChangeResources(() => updateToolbar())); this._disposables.value.add(this.storageService.onDidChangeValue(StorageScope.PROFILE, SCMInputWidgetStorageKey.LastActionId, this._disposables.value)(() => updateToolbar())); - this.actionRunner = new SCMInputWidgetActionRunner(input, this.storageService); + this.actionRunner = this._disposables.value.add(new SCMInputWidgetActionRunner(input, this.storageService)); this._disposables.value.add(this.actionRunner.onWillRun(e => { if ((this.actionRunner as SCMInputWidgetActionRunner).runningActions.size === 0) { super.setActions([this._cancelAction], []); From d5a710b8802c7a73ccbe06e00664de7cd62eb975 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:42:35 +0100 Subject: [PATCH 0315/2632] Fix NES change between side by side and line replace after partial typing (#239498) fix NES change between side by side and line replace after typing partially --- .../contrib/inlineCompletions/browser/view/inlineEdits/view.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts index b61c9947186b..4799e2d5d467 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts @@ -238,7 +238,7 @@ export class InlineEditsView extends Disposable { (this._useMixedLinesDiff.read(reader) === 'afterJumpWhenPossible' && this._previousView?.view !== 'mixedLines') || (this._useInterleavedLinesDiff.read(reader) === 'afterJump' && this._previousView?.view !== 'interleavedLines') ); - const reconsiderViewEditorWidthChange = this._previousView?.editorWidth !== this._editor.getLayoutInfo().width && + const reconsiderViewEditorWidthChange = this._previousView?.editorWidth !== this._editorObs.layoutInfoWidth.read(reader) && ( this._previousView?.view === 'sideBySide' || this._previousView?.view === 'lineReplacement' From edfff01528b5ce3741a6abf7215d7fb74182e5ac Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 3 Feb 2025 16:39:19 +0100 Subject: [PATCH 0316/2632] Fixing incorrect width sizing in expandable hover (#239501) fixing incorrect width sizing --- src/vs/editor/contrib/hover/browser/hover.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/hover/browser/hover.css b/src/vs/editor/contrib/hover/browser/hover.css index 2f5c23527133..9a00f2d4d031 100644 --- a/src/vs/editor/contrib/hover/browser/hover.css +++ b/src/vs/editor/contrib/hover/browser/hover.css @@ -46,7 +46,7 @@ .monaco-editor .monaco-hover .hover-row .verbosity-actions { border-right: 1px solid var(--vscode-editorHoverWidget-border); width: 22px; - overflow: hidden; + overflow-y: clip; } .monaco-editor .monaco-hover .hover-row .verbosity-actions-inner { From d8587cf8e73c9ebfd7008b708de92fe6eff59b10 Mon Sep 17 00:00:00 2001 From: Johannes Date: Mon, 3 Feb 2025 16:52:05 +0100 Subject: [PATCH 0317/2632] chore - rename and less use of `globalEditingSession` --- .../chat/browser/chatEditing/chatEditingActions.ts | 10 +++++----- .../chat/browser/chatEditing/chatEditingServiceImpl.ts | 2 +- .../chat/browser/contrib/chatInputCompletions.ts | 2 +- .../contrib/chat/common/chatEditingService.ts | 2 +- .../contrib/inlineChat/browser/inlineChatController.ts | 2 +- .../inlineChat/browser/inlineChatSessionServiceImpl.ts | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index 17573774ae7a..22ac94c44ced 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -528,7 +528,7 @@ registerAction2(class RemoveAction extends Action2 { return; } - const session = chatEditingService.globalEditingSession; + const session = chatEditingService.getEditingSession(chatModel.sessionId); if (!session) { return; } @@ -620,17 +620,17 @@ registerAction2(class OpenWorkingSetHistoryAction extends Action2 { const editorService = accessor.get(IEditorService); const chatModel = chatService.getSession(context.sessionId); - const requests = chatModel?.getRequests(); - if (!requests) { + if (!chatModel) { return; } - const snapshotRequestIndex = requests?.findIndex((v, i) => i > 0 && requests[i - 1]?.id === context.requestId); + const requests = chatModel.getRequests(); + const snapshotRequestIndex = requests.findIndex((v, i) => i > 0 && requests[i - 1]?.id === context.requestId); if (snapshotRequestIndex < 1) { return; } const snapshotRequestId = requests[snapshotRequestIndex]?.id; if (snapshotRequestId) { - const snapshot = chatEditingService.globalEditingSession?.getSnapshotUri(snapshotRequestId, context.uri); + const snapshot = chatEditingService.getEditingSession(chatModel.sessionId)?.getSnapshotUri(snapshotRequestId, context.uri); if (snapshot) { const editor = await editorService.openEditor({ resource: snapshot, label: localize('chatEditing.snapshot', '{0} (Snapshot {1})', basename(context.uri), snapshotRequestIndex - 1), options: { transient: true, activation: EditorActivation.ACTIVATE } }); if (isCodeEditor(editor)) { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts index 95d6d3330fa4..e49f8637673a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts @@ -213,7 +213,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic .find(candidate => candidate.chatSessionId === chatSessionId); } - async createAdhocEditingSession(chatSessionId: string): Promise { + async createEditingSession(chatSessionId: string): Promise { const session = this._instantiationService.createInstance(ChatEditingSession, chatSessionId, false, this._editingSessionFileLimitPromise, this._lookupEntry.bind(this)); await session.init(); diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index 69c400a312c8..717ed7e796a4 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -572,7 +572,7 @@ class BuiltinDynamicCompletions extends Disposable { const len = result.suggestions.length; // RELATED FILES - if (widget.location === ChatAgentLocation.EditingSession && widget.viewModel && this._chatEditingService.globalEditingSessionObs.get()?.chatSessionId === widget.viewModel?.sessionId) { + if (widget.location === ChatAgentLocation.EditingSession && widget.viewModel && this._chatEditingService.getEditingSession(widget.viewModel.sessionId)) { const relatedFiles = (await raceTimeout(this._chatEditingService.getRelatedFiles(widget.viewModel.sessionId, widget.getInput(), token), 200)) ?? []; for (const relatedFileGroup of relatedFiles) { for (const relatedFile of relatedFileGroup.files) { diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index 3a2b40178e2b..dc2a4a951209 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -40,7 +40,7 @@ export interface IChatEditingService { /** * Creates a new short lived editing session */ - createAdhocEditingSession(chatSessionId: string): Promise; + createEditingSession(chatSessionId: string): Promise; readonly editingSessionFileLimit: number; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 9c6b69b3e4bb..6ca0b87ae9d1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -1139,7 +1139,7 @@ export class InlineChatController implements IEditorContribution { const uri = this._editor.getModel().uri; const chatModel = this._chatService.startSession(ChatAgentLocation.Editor, token); - const editSession = await this._chatEditingService.createAdhocEditingSession(chatModel.sessionId); + const editSession = await this._chatEditingService.createEditingSession(chatModel.sessionId); // const store = new DisposableStore(); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts index b502148e9a4a..fe4a356ad200 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSessionServiceImpl.ts @@ -337,7 +337,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService { const chatModel = this._chatService.startSession(ChatAgentLocation.EditingSession, token); - const editingSession = await this._chatEditingService.createAdhocEditingSession(chatModel.sessionId); + const editingSession = await this._chatEditingService.createEditingSession(chatModel.sessionId); editingSession.addFileToWorkingSet(uri); const store = new DisposableStore(); From 25418c6a590442002093b95e8764e59c551e7712 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 3 Feb 2025 17:10:13 +0100 Subject: [PATCH 0318/2632] Adopt `getTitleBarStyle` to know if custom title is used (fix #238921) (#239496) --- src/vs/workbench/contrib/debug/browser/debugToolBar.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 2cbbdc5602aa..683bdf081209 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -32,7 +32,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { widgetBorder, widgetShadow } from '../../../../platform/theme/common/colorRegistry.js'; import { IThemeService, Themable } from '../../../../platform/theme/common/themeService.js'; -import { TitleBarSetting } from '../../../../platform/window/common/window.js'; +import { getTitleBarStyle, TitlebarStyle } from '../../../../platform/window/common/window.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { EditorTabsMode, IWorkbenchLayoutService, LayoutSettings, Parts } from '../../../services/layout/browser/layoutService.js'; import { CONTEXT_DEBUG_STATE, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_IN_DEBUG_MODE, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugConfiguration, IDebugService, State, VIEWLET_ID } from '../common/debug.js'; @@ -80,7 +80,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this.$el = dom.$('div.debug-toolbar'); // Note: changes to this setting require a restart, so no need to listen to it. - const customTitleBar = this.configurationService.getValue(TitleBarSetting.TITLE_BAR_STYLE) === 'custom'; + const customTitleBar = getTitleBarStyle(this.configurationService) === TitlebarStyle.CUSTOM; // Do not allow the widget to overflow or underflow window controls. // Use CSS calculations to avoid having to force layout with `.clientWidth` From b62486810ebb372fcb6a91da19543f210243894a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 3 Feb 2025 17:50:42 +0100 Subject: [PATCH 0319/2632] fix grammar (#239505) --- .../contrib/extensions/browser/extensions.contribution.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index bfa6ded54f1d..ddbfb82a80d7 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -1873,7 +1873,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi this.registerExtensionAction({ id: 'workbench.extensions.action.manageTrustedPublishers', - title: localize2('workbench.extensions.action.manageTrustedPublishers', "Manage Trusted Extensions Publishers"), + title: localize2('workbench.extensions.action.manageTrustedPublishers', "Manage Trusted Extension Publishers"), category: EXTENSIONS_CATEGORY, f1: true, run: async (accessor: ServicesAccessor) => { @@ -1888,7 +1888,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi })).sort((a, b) => a.label.localeCompare(b.label)); const result = await quickInputService.pick(trustedPublisherItems, { canPickMany: true, - title: localize('trustedPublishers', "Manage Trusted Extensions Publishers"), + title: localize('trustedPublishers', "Manage Trusted Extension Publishers"), placeHolder: localize('trustedPublishersPlaceholder', "Choose which publishers to trust"), }); if (result) { From 7b7de00c8d23595cafb7a7bd1aeac12c5646478f Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Mon, 3 Feb 2025 09:53:30 -0800 Subject: [PATCH 0320/2632] fix typo in the documentation link title (#239509) [ui]: fix typo in the documentation link title --- .../contrib/chat/browser/actions/chatContextActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index f4590dbb70d5..007fca5f49d4 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -967,7 +967,7 @@ const selectPromptAttachment = async (options: ISelectPromptOptions): Promise Date: Mon, 3 Feb 2025 09:57:07 -0800 Subject: [PATCH 0321/2632] fix prompt file resolve logic for empty workspaces (#239508) [config]: fix prompt file resolve logic for empty workspaces --- .../chatInstructionsFileLocator.ts | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatInstructionsFileLocator.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatInstructionsFileLocator.ts index 99d0cf7dd3bc..2d09533d6725 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatInstructionsFileLocator.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatInstructionsFileLocator.ts @@ -7,8 +7,8 @@ import { URI } from '../../../../../base/common/uri.js'; import { PromptFilesConfig } from '../../common/promptSyntax/config.js'; import { dirname, extUri } from '../../../../../base/common/resources.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; +import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; -import { IWorkspaceContextService, WorkbenchState } from '../../../../../platform/workspace/common/workspace.js'; import { PROMPT_SNIPPET_FILE_EXTENSION } from '../../common/promptSyntax/contentProviders/promptContentsProviderBase.js'; /** @@ -50,26 +50,26 @@ export class ChatInstructionsFileLocator { * @returns List of possible prompt instructions file locations. */ private getSourceLocations(): readonly URI[] { - const state = this.workspaceService.getWorkbenchState(); - - // nothing to do if the workspace is empty - if (state === WorkbenchState.EMPTY) { - return []; - } - const sourceLocations = PromptFilesConfig.sourceLocations(this.configService); const result = []; // otherwise for each folder provided in the configuration, create // a URI per each folder in the current workspace - const { folders } = this.workspaceService.getWorkspace(); - const workspaceRootUri = dirname(folders[0].uri); - for (const folder of folders) { - for (const sourceFolderName of sourceLocations) { + for (const sourceFolderName of sourceLocations) { + // if source folder is an absolute path, add the path as is + // without trying to resolve it against the workspace folders + const sourceFolderUri = URI.file(sourceFolderName); + if (sourceFolderUri.path === sourceFolderName) { + result.push(sourceFolderUri); + continue; + } + + const { folders } = this.workspaceService.getWorkspace(); + for (const folder of folders) { // create the source path as a path relative to the workspace // folder, or as an absolute path if the absolute value is provided - const sourceFolderUri = extUri.resolvePath(folder.uri, sourceFolderName); - result.push(sourceFolderUri); + const relativeFolderUri = extUri.resolvePath(folder.uri, sourceFolderName); + result.push(relativeFolderUri); // if not inside a workspace, we are done if (folders.length <= 1) { @@ -79,6 +79,7 @@ export class ChatInstructionsFileLocator { // if inside a workspace, consider the specified source location inside // the workspace root, to allow users to use some (e.g., `.github/prompts`) // folder as a top-level folder in the workspace + const workspaceRootUri = dirname(folders[0].uri); const workspaceFolderUri = extUri.resolvePath(workspaceRootUri, sourceFolderName); if (workspaceFolderUri.fsPath.startsWith(folder.uri.fsPath)) { result.push(workspaceFolderUri); From 63d1401deeb796b3bf5e2ace0ed58fe0675a5de8 Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Mon, 3 Feb 2025 13:54:18 -0500 Subject: [PATCH 0322/2632] fix: add electron as an external for webpack (#239134) * fix: add electron as an external for webpack * refactor: move electron external to shared webpack --- extensions/github-authentication/extension.webpack.config.js | 2 +- extensions/shared.webpack.config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/github-authentication/extension.webpack.config.js b/extensions/github-authentication/extension.webpack.config.js index df3adb4ee7a3..d356151d68c5 100644 --- a/extensions/github-authentication/extension.webpack.config.js +++ b/extensions/github-authentication/extension.webpack.config.js @@ -13,5 +13,5 @@ module.exports = withDefaults({ context: __dirname, entry: { extension: './src/extension.ts', - } + }, }); diff --git a/extensions/shared.webpack.config.js b/extensions/shared.webpack.config.js index 279bc199bc4a..ad9d70c24908 100644 --- a/extensions/shared.webpack.config.js +++ b/extensions/shared.webpack.config.js @@ -56,6 +56,7 @@ function withNodeDefaults(/**@type WebpackConfig & { context: string }*/extConfi }] }, externals: { + 'electron': 'commonjs electron', // ignored to avoid bundling from node_modules 'vscode': 'commonjs vscode', // ignored because it doesn't exist, 'applicationinsights-native-metrics': 'commonjs applicationinsights-native-metrics', // ignored because we don't ship native module '@azure/functions-core': 'commonjs azure/functions-core', // optioinal dependency of appinsights that we don't use @@ -204,4 +205,3 @@ module.exports.node = withNodeDefaults; module.exports.browser = withBrowserDefaults; module.exports.nodePlugins = nodePlugins; module.exports.browserPlugins = browserPlugins; - From 2f57cf2b2fcc601b57c06a16652a4bcc583c71bb Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Mon, 3 Feb 2025 10:58:57 -0800 Subject: [PATCH 0323/2632] dedup prompt file locations defined in settings (#239512) * [config]: remove duplicated source location paths * [config]: simplify the implementation --- .../chatInstructionsFileLocator.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatInstructionsFileLocator.ts b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatInstructionsFileLocator.ts index 2d09533d6725..f8d81c94cebd 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatInstructionsFileLocator.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAttachmentModel/chatInstructionsFileLocator.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from '../../../../../base/common/uri.js'; +import { ResourceSet } from '../../../../../base/common/map.js'; import { PromptFilesConfig } from '../../common/promptSyntax/config.js'; import { dirname, extUri } from '../../../../../base/common/resources.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; @@ -50,8 +51,8 @@ export class ChatInstructionsFileLocator { * @returns List of possible prompt instructions file locations. */ private getSourceLocations(): readonly URI[] { + const paths = new ResourceSet(); const sourceLocations = PromptFilesConfig.sourceLocations(this.configService); - const result = []; // otherwise for each folder provided in the configuration, create // a URI per each folder in the current workspace @@ -60,7 +61,11 @@ export class ChatInstructionsFileLocator { // without trying to resolve it against the workspace folders const sourceFolderUri = URI.file(sourceFolderName); if (sourceFolderUri.path === sourceFolderName) { - result.push(sourceFolderUri); + if (paths.has(sourceFolderUri)) { + continue; + } + + paths.add(sourceFolderUri); continue; } @@ -69,7 +74,9 @@ export class ChatInstructionsFileLocator { // create the source path as a path relative to the workspace // folder, or as an absolute path if the absolute value is provided const relativeFolderUri = extUri.resolvePath(folder.uri, sourceFolderName); - result.push(relativeFolderUri); + if (!paths.has(relativeFolderUri)) { + paths.add(relativeFolderUri); + } // if not inside a workspace, we are done if (folders.length <= 1) { @@ -81,13 +88,21 @@ export class ChatInstructionsFileLocator { // folder as a top-level folder in the workspace const workspaceRootUri = dirname(folders[0].uri); const workspaceFolderUri = extUri.resolvePath(workspaceRootUri, sourceFolderName); + // if we already have this folder in the list, skip it + if (paths.has(workspaceFolderUri)) { + continue; + } + + // otherwise, if the source location is inside a top-level workspace folder, + // add it to the list of paths too; this helps to handle the case when a + // relative path must be resolved from `root` of the workspace if (workspaceFolderUri.fsPath.startsWith(folder.uri.fsPath)) { - result.push(workspaceFolderUri); + paths.add(workspaceFolderUri); } } } - return result; + return [...paths]; } /** @@ -139,6 +154,5 @@ export class ChatInstructionsFileLocator { } return files; - } } From 722c40ee76b501e3ca882c4031d0e598db47aad8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 3 Feb 2025 11:08:00 -0800 Subject: [PATCH 0324/2632] Update src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts --- .../suggest/browser/terminalCompletionService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index 9d1865bc3d78..7883784d6f07 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -290,7 +290,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo // - `c:/foo/|` -> `c:/foo/` if (foldersRequested) { resourceCompletions.push({ - label: lastWordFolder.length === 0 ? '.' : lastWordFolder, + label: lastWordFolder, provider, kind: TerminalCompletionItemKind.Folder, isDirectory: true, From a90fa289d52f7531a63cce1dc561cb1bbf4f78ba Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Mon, 3 Feb 2025 11:24:43 -0800 Subject: [PATCH 0325/2632] Add context menus to notebook global toolbar + breadcrumbs (#239523) * context menu in notebook global toolbar * breadcrumb context menu also * lil shift around * nit cleaning --- src/vs/platform/actions/common/actions.ts | 2 ++ .../browser/parts/editor/breadcrumbsControl.ts | 15 +++++++++++++-- .../notebook/browser/controller/layoutActions.ts | 7 ++----- .../browser/viewParts/notebookEditorToolbar.ts | 9 +++++++++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 0e10b0d232f2..3abdc9acf37f 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -79,6 +79,7 @@ export class MenuId { static readonly EditorTabsBarShowTabsSubmenu = new MenuId('EditorTabsBarShowTabsSubmenu'); static readonly EditorTabsBarShowTabsZenModeSubmenu = new MenuId('EditorTabsBarShowTabsZenModeSubmenu'); static readonly EditorActionsPositionSubmenu = new MenuId('EditorActionsPositionSubmenu'); + static readonly EditorBreadcrumbsContext = new MenuId('EditorBreadcrumbsContext'); static readonly ExplorerContext = new MenuId('ExplorerContext'); static readonly ExplorerContextShare = new MenuId('ExplorerContextShare'); static readonly ExtensionContext = new MenuId('ExtensionContext'); @@ -175,6 +176,7 @@ export class MenuId { static readonly ReplInputExecute = new MenuId('ReplInputExecute'); static readonly IssueReporter = new MenuId('IssueReporter'); static readonly NotebookToolbar = new MenuId('NotebookToolbar'); + static readonly NotebookToolbarContext = new MenuId('NotebookToolbarContext'); static readonly NotebookStickyScrollContext = new MenuId('NotebookStickyScrollContext'); static readonly NotebookCellTitle = new MenuId('NotebookCellTitle'); static readonly NotebookCellDelete = new MenuId('NotebookCellDelete'); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 9a2581907c41..a0036fb3f028 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -24,7 +24,7 @@ import { Categories } from '../../../../platform/action/common/actionCommonCateg import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; -import { IContextViewService } from '../../../../platform/contextview/browser/contextView.js'; +import { IContextMenuService, IContextViewService } from '../../../../platform/contextview/browser/contextView.js'; import { fillInSymbolsDragData, LocalSelectionTransfer } from '../../../../platform/dnd/browser/dnd.js'; import { FileKind, IFileService, IFileStat } from '../../../../platform/files/common/files.js'; import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; @@ -271,6 +271,7 @@ export class BreadcrumbsControl { @IFileService private readonly _fileService: IFileService, @IEditorService private readonly _editorService: IEditorService, @ILabelService private readonly _labelService: ILabelService, + @IContextMenuService private readonly contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @IBreadcrumbsService breadcrumbsService: IBreadcrumbsService ) { @@ -299,6 +300,14 @@ export class BreadcrumbsControl { this._disposables.add(breadcrumbsService.register(this._editorGroup.id, this._widget)); this.hide(); + + this._disposables.add(dom.addDisposableListener(this.domNode, dom.EventType.CONTEXT_MENU, e => { + const event = new StandardMouseEvent(dom.getWindow(this.domNode), e); + this.contextMenuService.showContextMenu({ + menuId: MenuId.EditorBreadcrumbsContext, + getAnchor: () => event, + }); + })); } dispose(): void { @@ -687,7 +696,9 @@ registerAction2(class ToggleBreadcrumb extends Action2 { { id: MenuId.MenubarAppearanceMenu, group: '4_editor', order: 2 }, { id: MenuId.NotebookToolbar, group: 'notebookLayout', order: 2 }, { id: MenuId.StickyScrollContext }, - { id: MenuId.NotebookStickyScrollContext, group: 'notebookView', order: 2 } + { id: MenuId.EditorBreadcrumbsContext }, + { id: MenuId.NotebookStickyScrollContext, group: 'notebookView', order: 2 }, + { id: MenuId.NotebookToolbarContext, group: 'notebookView', order: 2 } ] }); } diff --git a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts index df4c6f1fe559..a278485249fd 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts @@ -262,11 +262,8 @@ registerAction2(class ToggleNotebookStickyScroll extends Action2 { }, menu: [ { id: MenuId.CommandPalette }, - { - id: MenuId.NotebookStickyScrollContext, - group: 'notebookView', - order: 2 - } + { id: MenuId.NotebookStickyScrollContext, group: 'notebookView', order: 2 }, + { id: MenuId.NotebookToolbarContext, group: 'notebookView', order: 2 } ] }); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts index 13f928feb064..eda688c3999d 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from '../../../../../base/browser/dom.js'; +import { StandardMouseEvent } from '../../../../../base/browser/mouseEvent.js'; import { DomScrollableElement } from '../../../../../base/browser/ui/scrollbar/scrollableElement.js'; import { ToolBar } from '../../../../../base/browser/ui/toolbar/toolbar.js'; import { IAction, Separator } from '../../../../../base/common/actions.js'; @@ -279,6 +280,14 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { )(this._updatePerEditorChange, this)); this._registerNotebookActionsToolbar(); + + this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.CONTEXT_MENU, e => { + const event = new StandardMouseEvent(DOM.getWindow(this.domNode), e); + this.contextMenuService.showContextMenu({ + menuId: MenuId.NotebookToolbarContext, + getAnchor: () => event, + }); + })); } private _buildBody() { From 3e01abb473a86a5d3da88e7d757127eebfd24b31 Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Mon, 3 Feb 2025 13:01:13 -0800 Subject: [PATCH 0326/2632] more descriptions for chatReferenceBinary Data (#239528) * add more description to chatReferenceBinaryData * remove extra stuff --- src/vscode-dts/vscode.proposed.chatReferenceBinaryData.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vscode-dts/vscode.proposed.chatReferenceBinaryData.d.ts b/src/vscode-dts/vscode.proposed.chatReferenceBinaryData.d.ts index ec10006fbe60..081c24ad8c3a 100644 --- a/src/vscode-dts/vscode.proposed.chatReferenceBinaryData.d.ts +++ b/src/vscode-dts/vscode.proposed.chatReferenceBinaryData.d.ts @@ -19,13 +19,13 @@ declare module 'vscode' { readonly mimeType: string; /** - * Retrieves the binary data of the reference. + * Retrieves the binary data of the reference. This is primarily used to receive image attachments from the chat. * @returns A promise that resolves to the binary data as a Uint8Array. */ data(): Thenable; /** - * + * Retrieves a URI reference to the binary data, if available. */ readonly reference?: Uri; From 1d7bbf4c89651c204ba4538631baf22b69f5a7c2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 3 Feb 2025 14:15:48 -0800 Subject: [PATCH 0327/2632] Disable failing test temporarily Part of #239532 --- .../suggest/test/browser/simpleCompletionModel.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts b/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts index 706c1fc1f30b..92a62738e08f 100644 --- a/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts +++ b/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts @@ -92,7 +92,8 @@ suite('SimpleCompletionModel', function () { assertItems(createFolderItemsModel(...initial), expected); }); - test('should handle many files and folders correctly', function () { + // #239532 Failing on CI not locally? + test.skip('should handle many files and folders correctly', function () { // This is VS Code's root directory with some python items added that have special // sorting const items = [ From 81cbef2ccb371b0d67e7bf80261d9379153d9f9a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 3 Feb 2025 16:33:54 -0600 Subject: [PATCH 0328/2632] return files/folder completions unless specific options / args are provided (#239384) Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> --- .../src/terminalSuggestMain.ts | 20 +++++---- .../src/test/terminalSuggestMain.test.ts | 16 ++++--- .../contrib/tasks/common/problemCollectors.ts | 42 ++++++++++--------- 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 34232ebda325..7c375d4ba706 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -241,6 +241,7 @@ export async function getCompletionItemsFromSpecs( const precedingText = terminalContext.commandLine.slice(0, terminalContext.cursorPosition + 1); + let specificItemsProvided = false; for (const spec of specs) { const specLabels = getLabel(spec); @@ -273,6 +274,7 @@ export async function getCompletionItemsFromSpecs( items.push(...argsCompletionResult.items); filesRequested ||= argsCompletionResult.filesRequested; foldersRequested ||= argsCompletionResult.foldersRequested; + specificItemsProvided ||= argsCompletionResult.items.length > 0; } if (!argsCompletionResult?.items.length) { // Arg completions are more specific, only get options if those are not provided. @@ -281,15 +283,13 @@ export async function getCompletionItemsFromSpecs( items.push(...optionsCompletionResult.items); filesRequested ||= optionsCompletionResult.filesRequested; foldersRequested ||= optionsCompletionResult.foldersRequested; + specificItemsProvided ||= optionsCompletionResult.items.length > 0; } } } } - const shouldShowResourceCompletions = - (!terminalContext.commandLine.trim() || !items.length) && - !filesRequested && - !foldersRequested; + if (tokenType === TokenType.Command) { // Include builitin/available commands in the results @@ -299,11 +299,17 @@ export async function getCompletionItemsFromSpecs( items.push(createCompletionItem(terminalContext.cursorPosition, prefix, command, command.detail)); } } - } - - if (shouldShowResourceCompletions) { filesRequested = true; foldersRequested = true; + } else { + const shouldShowResourceCompletions = + !specificItemsProvided && + !filesRequested && + !foldersRequested; + if (shouldShowResourceCompletions) { + filesRequested = true; + foldersRequested = true; + } } let cwd: vscode.Uri | undefined; diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index 030e9d0db995..68a2abbb2566 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -46,7 +46,8 @@ function createCodeTestSpecs(executable: string): ITestSpec2[] { const typingTests: ITestSpec2[] = []; for (let i = 1; i < executable.length; i++) { - typingTests.push({ input: `${executable.slice(0, i)}|`, expectedCompletions: [executable] }); + const input = `${executable.slice(0, i)}|`; + typingTests.push({ input, expectedCompletions: [executable], expectedResourceRequests: input.endsWith(' ') ? undefined : { type: 'both', cwd: testCwd } }); } return [ @@ -85,6 +86,9 @@ const testSpecs2: ISuiteSpec[] = [ completionSpecs: [], availableCommands: [], testSpecs: [ + { input: '|', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testCwd } }, + { input: '|.', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testCwd } }, + { input: '|./', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testCwd } }, { input: 'fakecommand |', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testCwd } }, ] }, @@ -93,11 +97,13 @@ const testSpecs2: ISuiteSpec[] = [ completionSpecs: cdSpec, availableCommands: 'cd', testSpecs: [ + // Typing a path + { input: '.|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testCwd } }, + { input: './|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testCwd } }, + { input: './.|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testCwd } }, // Typing the command - // TODO: Shouldn't this also request file resources that contain "c" as you can run a file as a command? - { input: 'c|', expectedCompletions: ['cd'] }, - // TODO: Shouldn't this also request file resources that contain "cd" as you can run a file as a command? - { input: 'cd|', expectedCompletions: ['cd'] }, + { input: 'c|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testCwd } }, + { input: 'cd|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testCwd } }, // Basic arguments { input: 'cd |', expectedCompletions: ['~', '-'], expectedResourceRequests: { type: 'folders', cwd: testCwd } }, diff --git a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts index f9aabbce56c5..3cb325d21a74 100644 --- a/src/vs/workbench/contrib/tasks/common/problemCollectors.ts +++ b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts @@ -435,26 +435,30 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement }); this.modelListeners.add(this.modelService.onModelRemoved(modelEvent => { - let markerChanged: IDisposable | undefined = - Event.debounce(this.markerService.onMarkerChanged, (last: readonly URI[] | undefined, e: readonly URI[]) => { - return (last ?? []).concat(e); - }, 500, false, true)(async (markerEvent) => { - markerChanged?.dispose(); + let markerChanged: IDisposable | undefined = Event.debounce( + this.markerService.onMarkerChanged, + (last: readonly URI[] | undefined, e: readonly URI[]) => (last ?? []).concat(e), + 500, + false, + true + )(async (markerEvent: readonly URI[]) => { + if (!markerEvent || !markerEvent.includes(modelEvent.uri) || (this.markerService.read({ resource: modelEvent.uri }).length !== 0)) { + return; + } + const oldLines = Array.from(this.lines); + for (const line of oldLines) { + await this.processLineInternal(line); + } + }); + + this._register(markerChanged); // Ensures markerChanged is tracked and disposed of properly + + setTimeout(() => { + if (markerChanged) { + const _markerChanged = markerChanged; markerChanged = undefined; - if (!markerEvent || !markerEvent.includes(modelEvent.uri) || (this.markerService.read({ resource: modelEvent.uri }).length !== 0)) { - return; - } - const oldLines = Array.from(this.lines); - for (const line of oldLines) { - await this.processLineInternal(line); - } - }); - setTimeout(async () => { - // Calling dispose below can trigger the debounce event (via flushOnListenerRemove), so we - // have to unset markerChanged first to make sure the handler above doesn't dispose it again. - const _markerChanged = markerChanged; - markerChanged = undefined; - _markerChanged?.dispose(); + _markerChanged.dispose(); + } }, 600); })); } From 557130816284cfae86601274065b9f89db93cedb Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Mon, 3 Feb 2025 14:56:45 -0800 Subject: [PATCH 0329/2632] Force an update after acquiring a token interactively (#239539) * Force an update after acquiring a token interactively This will make sure the account cache is up-to-date before the acquireTokenInteractive ends. A greater fix is maybe turning the accounts cache to be a promise... bit this is the candidate fix for now. Fixes #235327 * also delete event --- .../src/node/authProvider.ts | 1 - .../src/node/cachedPublicClientApplication.ts | 27 ++++++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/extensions/microsoft-authentication/src/node/authProvider.ts b/extensions/microsoft-authentication/src/node/authProvider.ts index 0a352c8eb868..40000e086200 100644 --- a/extensions/microsoft-authentication/src/node/authProvider.ts +++ b/extensions/microsoft-authentication/src/node/authProvider.ts @@ -233,7 +233,6 @@ export class MsalAuthProvider implements AuthenticationProvider { const session = this.sessionFromAuthenticationResult(result, scopeData.originalScopes); this._telemetryReporter.sendLoginEvent(session.scopes); this._logger.info('[createSession]', `[${scopeData.scopeStr}]`, 'returned session'); - this._onDidChangeSessionsEmitter.fire({ added: [session], changed: [], removed: [] }); return session; } catch (e) { lastError = e; diff --git a/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts b/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts index a986b217983e..8d081f1a825b 100644 --- a/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts +++ b/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts @@ -149,22 +149,29 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica async acquireTokenInteractive(request: InteractiveRequest): Promise { this._logger.debug(`[acquireTokenInteractive] [${this._clientId}] [${this._authority}] [${request.scopes?.join(' ')}] loopbackClientOverride: ${request.loopbackClient ? 'true' : 'false'}`); - const result = await window.withProgress( + return await window.withProgress( { location: ProgressLocation.Notification, cancellable: true, title: l10n.t('Signing in to Microsoft...') }, - (_process, token) => this._sequencer.queue(() => raceCancellationAndTimeoutError( - this._pca.acquireTokenInteractive(request), - token, - 1000 * 60 * 5 - )) + (_process, token) => this._sequencer.queue(async () => { + const result = await raceCancellationAndTimeoutError( + this._pca.acquireTokenInteractive(request), + token, + 1000 * 60 * 5 + ); + if (this._isBrokerAvailable) { + await this._accountAccess.setAllowedAccess(result.account!, true); + } + // Force an update so that the account cache is updated. + // TODO:@TylerLeonhardt The problem is, we use the sequencer for + // change events but we _don't_ use it for the accounts cache. + // We should probably use it for the accounts cache as well. + await this._update(); + return result; + }) ); - if (this._isBrokerAvailable) { - await this._accountAccess.setAllowedAccess(result.account!, true); - } - return result; } /** From f4244472c731c63eb075f263a1fdc845e9a80589 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 4 Feb 2025 10:00:49 +1100 Subject: [PATCH 0330/2632] Enable Serialize ipynb in worker (#239453) --- extensions/ipynb/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index d9a9dd7a5143..1cf1efd4e913 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -43,7 +43,7 @@ "type": "boolean", "scope": "resource", "markdownDescription": "%ipynb.experimental.serialization%", - "default": false, + "default": true, "tags": [ "experimental" ] From 7775bb6fc9d99a41560c20562addccf326b14a5f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 3 Feb 2025 15:25:24 -0800 Subject: [PATCH 0331/2632] Re-enable skipped test Part of #239532 --- .../suggest/test/browser/simpleCompletionModel.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts b/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts index 92a62738e08f..706c1fc1f30b 100644 --- a/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts +++ b/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts @@ -92,8 +92,7 @@ suite('SimpleCompletionModel', function () { assertItems(createFolderItemsModel(...initial), expected); }); - // #239532 Failing on CI not locally? - test.skip('should handle many files and folders correctly', function () { + test('should handle many files and folders correctly', function () { // This is VS Code's root directory with some python items added that have special // sorting const items = [ From 541aae4082f1d48bb1295e65216270936f8bf439 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Mon, 3 Feb 2025 17:29:57 -0600 Subject: [PATCH 0332/2632] rm unnecessary check (#239530) fix #239407 --- .../suggest/browser/terminalCompletionService.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts index cf4debc04f02..e91ff1ba9632 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalCompletionService.ts @@ -5,7 +5,6 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Disposable, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; -import { Schemas } from '../../../../../base/common/network.js'; import { basename } from '../../../../../base/common/path.js'; import { isWindows } from '../../../../../base/common/platform.js'; import { URI } from '../../../../../base/common/uri.js'; @@ -373,8 +372,7 @@ export class TerminalCompletionService extends Disposable implements ITerminalCo let kind: TerminalCompletionItemKind | undefined; if (foldersRequested && stat.isDirectory) { kind = TerminalCompletionItemKind.Folder; - } - if (filesRequested && !stat.isDirectory && (stat.isFile || stat.resource.scheme === Schemas.file)) { + } else if (filesRequested && stat.isFile) { kind = TerminalCompletionItemKind.File; } if (kind === undefined) { From 4b9eb5a7f0c469fc084e459017140b980b3d6bd6 Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Mon, 3 Feb 2025 15:31:33 -0800 Subject: [PATCH 0333/2632] improve layout of the prompt attachment (#239514) * [ui]: improve layout of the prompt attachment * [ui]: remove redundant outline style for monaco button * [ui]: remove redundant CSS styles --- .../instructionsAttachment.ts | 10 +++++++++- .../contrib/chat/browser/media/chat.css | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/attachments/instructionsAttachment/instructionsAttachment.ts b/src/vs/workbench/contrib/chat/browser/attachments/instructionsAttachment/instructionsAttachment.ts index f959d7eabf11..0cb9b04a7095 100644 --- a/src/vs/workbench/contrib/chat/browser/attachments/instructionsAttachment/instructionsAttachment.ts +++ b/src/vs/workbench/contrib/chat/browser/attachments/instructionsAttachment/instructionsAttachment.ts @@ -148,7 +148,15 @@ export class InstructionsAttachmentWidget extends Disposable { this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('element'), hintElement, title)); // create the `remove` button - const removeButton = this.renderDisposables.add(new Button(this.domNode, { supportIcons: true, title: localize('remove', "Remove") })); + const removeButton = this.renderDisposables.add( + new Button( + this.domNode, + { + supportIcons: true, + title: localize('remove', "Remove"), + }, + ), + ); removeButton.icon = Codicon.x; this.renderDisposables.add(removeButton.onDidClick((e) => { e.stopPropagation(); diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 17ac0e584427..9a0c57c3bc56 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -968,6 +968,7 @@ have to be updated for changes to the rules above, or to support more deeply nes .chat-attached-context .chat-prompt-instructions-attachment .chat-implicit-hint { opacity: 0.7; font-size: .9em; + margin-top: -0.5px; } .chat-attached-context .chat-prompt-instructions-attachment.warning { color: var(--vscode-notificationsWarningIcon-foreground); @@ -979,6 +980,20 @@ have to be updated for changes to the rules above, or to support more deeply nes border-style: dashed; opacity: 0.75; } +.chat-attached-context .chat-prompt-instructions-attachment .monaco-button { + border-left: 1px solid var(--vscode-chat-requestBorder, var(--vscode-input-background, transparent)); + margin-left: 3px; +} +.chat-attached-context .chat-prompt-instructions-attachment.error .monaco-button, +.chat-attached-context .chat-prompt-instructions-attachment.warning .monaco-button { + border-left-color: currentColor; +} +.chat-attached-context .chat-prompt-instructions-attachment:focus .monaco-button { + border-color: var(--vscode-focusBorder); +} +.chat-attached-context .chat-prompt-instructions-attachment .monaco-icon-label-container { + margin-top: -0.1em; +} /* * This overly-specific CSS selector is needed to beat priority of some * styles applied on the the `.chat-attached-context-attachment` element. @@ -987,6 +1002,10 @@ have to be updated for changes to the rules above, or to support more deeply nes .chat-attached-context .chat-prompt-instructions-attachments .chat-prompt-instructions-attachment.warning.implicit { border: 1px solid currentColor; } +.chat-attached-context .chat-prompt-instructions-attachments .chat-prompt-instructions-attachment.implicit .monaco-button { + padding-left: 2px; + padding-right: 2px; +} /* * If in one of the non-normal states, make sure the `main icon` of * the component has the same color as the component itself From a9b6512fe76b0804eb7e8614e80d98ced57f1a49 Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Mon, 3 Feb 2025 15:34:01 -0800 Subject: [PATCH 0334/2632] [debt]: remove unused config service injection (#239544) --- .../contrib/chatDynamicVariables/chatFileReference.ts | 4 +--- .../chat/common/promptSyntax/parsers/basePromptParser.ts | 7 ++----- .../chat/common/promptSyntax/parsers/filePromptParser.ts | 4 +--- .../common/promptSyntax/parsers/textModelPromptParser.ts | 4 +--- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables/chatFileReference.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables/chatFileReference.ts index d4a4574aeace..cb6ce3b9d2d9 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables/chatFileReference.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables/chatFileReference.ts @@ -9,7 +9,6 @@ import { IDynamicVariable } from '../../../common/chatVariables.js'; import { IRange } from '../../../../../../editor/common/core/range.js'; import { ILogService } from '../../../../../../platform/log/common/log.js'; import { FilePromptParser } from '../../../common/promptSyntax/parsers/filePromptParser.js'; -import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; /** @@ -24,7 +23,6 @@ export class ChatFileReference extends FilePromptParser implements IDynamicVaria constructor( public readonly reference: IDynamicVariable, @IInstantiationService initService: IInstantiationService, - @IConfigurationService configService: IConfigurationService, @ILogService logService: ILogService, ) { const { data } = reference; @@ -34,7 +32,7 @@ export class ChatFileReference extends FilePromptParser implements IDynamicVaria `Variable data must be an URI, got '${data}'.`, ); - super(data, [], initService, configService, logService); + super(data, [], initService, logService); } /** diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts index 9ef8199579b6..aa277f4b7166 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/basePromptParser.ts @@ -22,7 +22,6 @@ import { ObservableDisposable } from '../../../../../../base/common/observableDi import { FilePromptContentProvider } from '../contentProviders/filePromptContentsProvider.js'; import { PROMPT_SNIPPET_FILE_EXTENSION } from '../contentProviders/promptContentsProviderBase.js'; import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; -import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js'; import { MarkdownLink } from '../../../../../../editor/common/codecs/markdownCodec/tokens/markdownLink.js'; import { FileOpenFailed, NonPromptSnippetFile, RecursiveReference, ParseError, FailedToResolveContentsStream } from '../../promptFileReferenceErrors.js'; @@ -145,7 +144,6 @@ export abstract class BasePromptParser extend private readonly promptContentsProvider: T, seenReferences: string[] = [], @IInstantiationService protected readonly instantiationService: IInstantiationService, - @IConfigurationService protected readonly configService: IConfigurationService, @ILogService protected readonly logService: ILogService, ) { super(); @@ -173,7 +171,7 @@ export abstract class BasePromptParser extend this._register( this.promptContentsProvider.onContentChanged((streamOrError) => { - // process the the received message + // process the received message this.onContentsChanged(streamOrError, seenReferences); // indicate that we've received at least one `onContentChanged` event @@ -559,13 +557,12 @@ export class PromptFileReference extends BasePromptParser Date: Tue, 4 Feb 2025 00:51:35 +0100 Subject: [PATCH 0335/2632] SCM - add quickDiffDecorationCount context key (#239547) --- .../contrib/scm/browser/quickDiffDecorator.ts | 48 ++++++++++++++++++- .../contrib/scm/browser/quickDiffWidget.ts | 5 +- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/quickDiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/quickDiffDecorator.ts index 8e701d3d92c3..01c9eb2173c2 100644 --- a/src/vs/workbench/contrib/scm/browser/quickDiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/quickDiffDecorator.ts @@ -21,6 +21,10 @@ import { QuickDiffModel, IQuickDiffModelService } from './quickDiffModel.js'; import { IWorkbenchContribution } from '../../../common/contributions.js'; import { ResourceMap } from '../../../../base/common/map.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; +import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { autorun, autorunWithStore, observableFromEvent } from '../../../../base/common/observable.js'; + +export const quickDiffDecorationCount = new RawContextKey('quickDiffDecorationCount', 0); class QuickDiffDecorator extends Disposable { @@ -166,7 +170,7 @@ class QuickDiffDecorator extends Disposable { override dispose(): void { if (this.decorationsCollection) { - this.decorationsCollection?.clear(); + this.decorationsCollection.clear(); } this.decorationsCollection = undefined; this.quickDiffModelRef.dispose(); @@ -182,6 +186,10 @@ interface QuickDiffWorkbenchControllerViewState { export class QuickDiffWorkbenchController extends Disposable implements IWorkbenchContribution { private enabled = false; + private readonly quickDiffDecorationCount: IContextKey; + + private readonly activeEditor = observableFromEvent(this, + this.editorService.onDidActiveEditorChange, () => this.editorService.activeEditor); // Resource URI -> Code Editor Id -> Decoration (Disposable) private readonly decorators = new ResourceMap>(); @@ -194,10 +202,13 @@ export class QuickDiffWorkbenchController extends Disposable implements IWorkben @IConfigurationService private readonly configurationService: IConfigurationService, @IQuickDiffModelService private readonly quickDiffModelService: IQuickDiffModelService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IContextKeyService contextKeyService: IContextKeyService, ) { super(); this.stylesheet = domStylesheetsJs.createStyleSheet(undefined, undefined, this._store); + this.quickDiffDecorationCount = quickDiffDecorationCount.bindTo(contextKeyService); + const onDidChangeConfiguration = Event.filter(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.diffDecorations')); this._register(onDidChangeConfiguration(this.onDidChangeConfiguration, this)); this.onDidChangeConfiguration(); @@ -266,6 +277,9 @@ export class QuickDiffWorkbenchController extends Disposable implements IWorkben this.transientDisposables.add(Event.any(this.editorService.onDidCloseEditor, this.editorService.onDidVisibleEditorsChange)(() => this.onEditorsChanged())); this.onEditorsChanged(); + + this.onDidActiveEditorChange(); + this.enabled = true; } @@ -275,6 +289,7 @@ export class QuickDiffWorkbenchController extends Disposable implements IWorkben } this.transientDisposables.clear(); + this.quickDiffDecorationCount.set(0); for (const [uri, decoratorMap] of this.decorators.entries()) { decoratorMap.dispose(); @@ -284,6 +299,37 @@ export class QuickDiffWorkbenchController extends Disposable implements IWorkben this.enabled = false; } + private onDidActiveEditorChange(): void { + this.transientDisposables.add(autorunWithStore((reader, store) => { + const activeEditor = this.activeEditor.read(reader); + const activeTextEditorControl = this.editorService.activeTextEditorControl; + + if (!isCodeEditor(activeTextEditorControl) || !activeEditor?.resource) { + this.quickDiffDecorationCount.set(0); + return; + } + + const quickDiffModelRef = this.quickDiffModelService.createQuickDiffModelReference(activeEditor.resource); + if (!quickDiffModelRef) { + this.quickDiffDecorationCount.set(0); + return; + } + + store.add(quickDiffModelRef); + + const visibleDecorationCount = observableFromEvent(this, + quickDiffModelRef.object.onDidChange, () => { + const visibleQuickDiffs = quickDiffModelRef.object.quickDiffs.filter(quickDiff => quickDiff.visible); + return quickDiffModelRef.object.changes.filter(labeledChange => visibleQuickDiffs.some(quickDiff => quickDiff.label === labeledChange.label)).length; + }); + + store.add(autorun(reader => { + const count = visibleDecorationCount.read(reader); + this.quickDiffDecorationCount.set(count); + })); + })); + } + private onEditorsChanged(): void { for (const editor of this.editorService.visibleTextEditorControls) { if (!isCodeEditor(editor)) { diff --git a/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts b/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts index 37ed63fb396f..791a1eb7f317 100644 --- a/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts +++ b/src/vs/workbench/contrib/scm/browser/quickDiffWidget.ts @@ -49,6 +49,7 @@ import { Codicon } from '../../../../base/common/codicons.js'; import { Color } from '../../../../base/common/color.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { getOuterEditor } from '../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js'; +import { quickDiffDecorationCount } from './quickDiffDecorator.js'; export const isQuickDiffVisible = new RawContextKey('dirtyDiffVisible', false); @@ -804,7 +805,7 @@ export class GotoPreviousChangeAction extends EditorAction { super({ id: 'workbench.action.editor.previousChange', label: nls.localize2('move to previous change', "Go to Previous Change"), - precondition: TextCompareEditorActiveContext.toNegated(), + precondition: ContextKeyExpr.and(TextCompareEditorActiveContext.toNegated(), quickDiffDecorationCount.notEqualsTo(0)), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.F5, weight: KeybindingWeight.EditorContrib } }); } @@ -844,7 +845,7 @@ export class GotoNextChangeAction extends EditorAction { super({ id: 'workbench.action.editor.nextChange', label: nls.localize2('move to next change', "Go to Next Change"), - precondition: TextCompareEditorActiveContext.toNegated(), + precondition: ContextKeyExpr.and(TextCompareEditorActiveContext.toNegated(), quickDiffDecorationCount.notEqualsTo(0)), kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Alt | KeyCode.F5, weight: KeybindingWeight.EditorContrib } }); } From 6bc5734484b8a91569241a08376790aa568767dd Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Mon, 3 Feb 2025 21:41:17 -0800 Subject: [PATCH 0336/2632] [debt]: pull out common constants into a standalone module (#239560) --- .../browser/actions/chatContextActions.ts | 11 +++++----- .../instructionsAttachment.ts | 4 ++-- .../chatInstructionsFileLocator.ts | 4 ++-- .../chat/common/promptSyntax/config.ts | 9 ++------ .../chat/common/promptSyntax/constants.ts | 21 +++++++++++++++++++ .../promptContentsProviderBase.ts | 8 ++----- .../languageFeatures/promptLinkProvider.ts | 11 ++-------- .../promptSyntax/parsers/basePromptParser.ts | 4 ++-- 8 files changed, 38 insertions(+), 34 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/constants.ts diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index 007fca5f49d4..cdd4135f32b5 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -51,8 +51,7 @@ import { IChatRequestVariableEntry } from '../../common/chatModel.js'; import { ChatRequestAgentPart } from '../../common/chatParserTypes.js'; import { IChatVariableData, IChatVariablesService } from '../../common/chatVariables.js'; import { ILanguageModelToolsService } from '../../common/languageModelToolsService.js'; -import { PromptFilesConfig } from '../../common/promptSyntax/config.js'; -import { PROMPT_SNIPPET_FILE_EXTENSION } from '../../common/promptSyntax/contentProviders/promptContentsProviderBase.js'; +import { DOCUMENTATION_URL, PROMPT_FILE_EXTENSION } from '../../common/promptSyntax/constants.js'; import { IChatWidget, IChatWidgetService, IQuickChatService, showChatView, showEditsView } from '../chat.js'; import { imageToHash, isImage } from '../chatPasteProviders.js'; import { isQuickChat } from '../chatWidget.js'; @@ -949,7 +948,7 @@ const selectPromptAttachment = async (options: ISelectPromptOptions): Promise { return files.map((file) => { const fileBasename = basename(file); - const fileWithoutExtension = fileBasename.replace(PROMPT_SNIPPET_FILE_EXTENSION, ''); + const fileWithoutExtension = fileBasename.replace(PROMPT_FILE_EXTENSION, ''); const result: IQuickPickItem & { value: URI } = { type: 'item', label: fileWithoutExtension, @@ -968,9 +967,9 @@ const selectPromptAttachment = async (options: ISelectPromptOptions): Promise extend * Check if the provided URI points to a prompt snippet. */ public static isPromptSnippet(uri: URI): boolean { - return uri.path.endsWith(PROMPT_SNIPPET_FILE_EXTENSION); + return uri.path.endsWith(PROMPT_FILE_EXTENSION); } /** From 802eba821f216155e8164eb8469b4ca9c9e2da57 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 4 Feb 2025 14:55:33 +0800 Subject: [PATCH 0337/2632] refactor: clean up unused `detectedParticipant` API (#239566) --- .../workbench/api/common/extHost.api.impl.ts | 1 - .../api/common/extHostChatAgents2.ts | 10 ---------- .../api/common/extHostTypeConverters.ts | 19 ++----------------- src/vs/workbench/api/common/extHostTypes.ts | 10 ---------- .../contrib/chat/common/chatModel.ts | 6 ------ .../contrib/chat/common/chatService.ts | 7 ------- ...ode.proposed.chatParticipantAdditions.d.ts | 12 ++---------- 7 files changed, 4 insertions(+), 61 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index d058e61b0da9..7766c78231fc 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1777,7 +1777,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I ChatResponseTextEditPart: extHostTypes.ChatResponseTextEditPart, ChatResponseMarkdownWithVulnerabilitiesPart: extHostTypes.ChatResponseMarkdownWithVulnerabilitiesPart, ChatResponseCommandButtonPart: extHostTypes.ChatResponseCommandButtonPart, - ChatResponseDetectedParticipantPart: extHostTypes.ChatResponseDetectedParticipantPart, ChatResponseConfirmationPart: extHostTypes.ChatResponseConfirmationPart, ChatResponseMovePart: extHostTypes.ChatResponseMovePart, ChatResponseReferencePartStatusKind: extHostTypes.ChatResponseReferencePartStatusKind, diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 4bf84b803aab..0806fa58b72a 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -224,15 +224,6 @@ class ChatAgentResponseStream { _report(dto); return this; }, - detectedParticipant(participant, command) { - throwIfDone(this.detectedParticipant); - checkProposedApiEnabled(that._extension, 'chatParticipantAdditions'); - - const part = new extHostTypes.ChatResponseDetectedParticipantPart(participant, command); - const dto = typeConvert.ChatResponseDetectedParticipantPart.from(part); - _report(dto); - return this; - }, confirmation(title, message, data, buttons) { throwIfDone(this.confirmation); checkProposedApiEnabled(that._extension, 'chatParticipantAdditions'); @@ -248,7 +239,6 @@ class ChatAgentResponseStream { if ( part instanceof extHostTypes.ChatResponseTextEditPart || part instanceof extHostTypes.ChatResponseMarkdownWithVulnerabilitiesPart || - part instanceof extHostTypes.ChatResponseDetectedParticipantPart || part instanceof extHostTypes.ChatResponseWarningPart || part instanceof extHostTypes.ChatResponseConfirmationPart || part instanceof extHostTypes.ChatResponseCodeCitationPart || diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 7613e42da8d1..82cf5f714944 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -38,7 +38,7 @@ import { DEFAULT_EDITOR_ASSOCIATION, SaveReason } from '../../common/editor.js'; import { IViewBadge } from '../../common/views.js'; import { ChatAgentLocation, IChatAgentRequest, IChatAgentResult } from '../../contrib/chat/common/chatAgents.js'; import { IChatRequestVariableEntry } from '../../contrib/chat/common/chatModel.js'; -import { IChatAgentDetection, IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatMoveMessage, IChatProgressMessage, IChatResponseCodeblockUriPart, IChatTaskDto, IChatTaskResult, IChatTextEdit, IChatTreeData, IChatUserActionEvent, IChatWarningMessage } from '../../contrib/chat/common/chatService.js'; +import { IChatAgentMarkdownContentWithVulnerability, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatMoveMessage, IChatProgressMessage, IChatResponseCodeblockUriPart, IChatTaskDto, IChatTaskResult, IChatTextEdit, IChatTreeData, IChatUserActionEvent, IChatWarningMessage } from '../../contrib/chat/common/chatService.js'; import { IToolData, IToolResult } from '../../contrib/chat/common/languageModelToolsService.js'; import * as chatProvider from '../../contrib/chat/common/languageModels.js'; import { DebugTreeItemCollapsibleState, IDebugVisualizationTreeItem } from '../../contrib/debug/common/debug.js'; @@ -2430,19 +2430,6 @@ export namespace ChatResponseMarkdownWithVulnerabilitiesPart { } } -export namespace ChatResponseDetectedParticipantPart { - export function from(part: vscode.ChatResponseDetectedParticipantPart): Dto { - return { - kind: 'agentDetection', - agentId: part.participant, - command: part.command, - }; - } - export function to(part: Dto): vscode.ChatResponseDetectedParticipantPart { - return new types.ChatResponseDetectedParticipantPart(part.agentId, part.command); - } -} - export namespace ChatResponseConfirmationPart { export function from(part: vscode.ChatResponseConfirmationPart): Dto { return { @@ -2671,7 +2658,7 @@ export namespace ChatResponseCodeCitationPart { export namespace ChatResponsePart { - export function from(part: vscode.ChatResponsePart | vscode.ChatResponseTextEditPart | vscode.ChatResponseMarkdownWithVulnerabilitiesPart | vscode.ChatResponseDetectedParticipantPart | vscode.ChatResponseWarningPart | vscode.ChatResponseConfirmationPart | vscode.ChatResponseReferencePart2 | vscode.ChatResponseMovePart, commandsConverter: CommandsConverter, commandDisposables: DisposableStore): extHostProtocol.IChatProgressDto { + export function from(part: vscode.ChatResponsePart | vscode.ChatResponseTextEditPart | vscode.ChatResponseMarkdownWithVulnerabilitiesPart | vscode.ChatResponseWarningPart | vscode.ChatResponseConfirmationPart | vscode.ChatResponseReferencePart2 | vscode.ChatResponseMovePart, commandsConverter: CommandsConverter, commandDisposables: DisposableStore): extHostProtocol.IChatProgressDto { if (part instanceof types.ChatResponseMarkdownPart) { return ChatResponseMarkdownPart.from(part); } else if (part instanceof types.ChatResponseAnchorPart) { @@ -2690,8 +2677,6 @@ export namespace ChatResponsePart { return ChatResponseMarkdownWithVulnerabilitiesPart.from(part); } else if (part instanceof types.ChatResponseCodeblockUriPart) { return ChatResponseCodeblockUriPart.from(part); - } else if (part instanceof types.ChatResponseDetectedParticipantPart) { - return ChatResponseDetectedParticipantPart.from(part); } else if (part instanceof types.ChatResponseWarningPart) { return ChatResponseWarningPart.from(part); } else if (part instanceof types.ChatResponseConfirmationPart) { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 4130d79f0ab7..ec4a0874ad57 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4517,16 +4517,6 @@ export class ChatResponseMarkdownWithVulnerabilitiesPart { } } -export class ChatResponseDetectedParticipantPart { - participant: string; - // TODO@API validate this against statically-declared slash commands? - command?: vscode.ChatCommand; - constructor(participant: string, command?: vscode.ChatCommand) { - this.participant = participant; - this.command = command; - } -} - export class ChatResponseConfirmationPart { title: string; message: string; diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 406896357199..9f99ef906786 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -1342,12 +1342,6 @@ export class ChatModel extends Disposable implements IChatModel { request.response.updateContent(progress, quiet); } else if (progress.kind === 'usedContext' || progress.kind === 'reference') { request.response.applyReference(progress); - } else if (progress.kind === 'agentDetection') { - const agent = this.chatAgentService.getAgent(progress.agentId); - if (agent) { - request.response.setAgent(agent, progress.command); - this._onDidChange.fire({ kind: 'setAgent', agent, command: progress.command }); - } } else if (progress.kind === 'codeCitation') { request.response.applyCodeCitation(progress); } else if (progress.kind === 'move') { diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 4251fe543566..6788a38ec4df 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -105,12 +105,6 @@ export interface IChatContentInlineReference { kind: 'inlineReference'; } -export interface IChatAgentDetection { - agentId: string; - command?: IChatAgentCommand; - kind: 'agentDetection'; -} - export interface IChatMarkdownContent { content: IMarkdownString; inlineReferences?: Record; @@ -231,7 +225,6 @@ export type IChatProgress = | IChatContentReference | IChatContentInlineReference | IChatCodeCitation - | IChatAgentDetection | IChatProgressMessage | IChatTask | IChatTaskResult diff --git a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts index 374ff087cc17..3fafc42e7a45 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts @@ -17,13 +17,6 @@ declare module 'vscode' { readonly description: string; } - export class ChatResponseDetectedParticipantPart { - participant: string; - // TODO@API validate this against statically-declared slash commands? - command?: ChatCommand; - constructor(participant: string, command?: ChatCommand); - } - export interface ChatVulnerability { title: string; description: string; @@ -77,7 +70,7 @@ declare module 'vscode' { constructor(value: Uri, license: string, snippet: string); } - export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseDetectedParticipantPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart; + export type ExtendedChatResponsePart = ChatResponsePart | ChatResponseTextEditPart | ChatResponseConfirmationPart | ChatResponseCodeCitationPart | ChatResponseReferencePart2 | ChatResponseMovePart; export class ChatResponseWarningPart { value: MarkdownString; @@ -175,8 +168,7 @@ declare module 'vscode' { markdownWithVulnerabilities(value: string | MarkdownString, vulnerabilities: ChatVulnerability[]): void; codeblockUri(uri: Uri): void; - detectedParticipant(participant: string, command?: ChatCommand): void; - push(part: ChatResponsePart | ChatResponseTextEditPart | ChatResponseDetectedParticipantPart | ChatResponseWarningPart | ChatResponseProgressPart2): void; + push(part: ChatResponsePart | ChatResponseTextEditPart | ChatResponseWarningPart | ChatResponseProgressPart2): void; /** * Show an inline message in the chat view asking the user to confirm an action. From 01e36597fe244ed478f0849efbad1a82243c894a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 4 Feb 2025 09:42:34 +0100 Subject: [PATCH 0338/2632] fix a.setTimeout is not a function (#239572) --- .../extensionManagement/common/extensionGalleryService.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 90b110c251ae..7975c97c5126 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -1569,7 +1569,6 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const context = await this.requestService.request({ type: 'GET', url: this.extensionsControlUrl, - timeout: 10000 /*10s*/ }, CancellationToken.None); if (context.res.statusCode !== 200) { From c9948b69ee93cebd049925aa9d6176589863644c Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 4 Feb 2025 10:29:01 +0100 Subject: [PATCH 0339/2632] Update grammars (#239483) Fixes #239353 --- extensions/csharp/cgmanifest.json | 2 +- .../csharp/syntaxes/csharp.tmLanguage.json | 4 +- extensions/latex/cgmanifest.json | 4 +- .../latex/syntaxes/LaTeX.tmLanguage.json | 47 +- .../cpp-grammar-bailout.tmLanguage.json | 1924 +++++++++++++---- .../markdown-latex-combined.tmLanguage.json | 32 +- extensions/log/cgmanifest.json | 4 +- extensions/log/syntaxes/log.tmLanguage.json | 10 +- extensions/perl/cgmanifest.json | 2 +- extensions/razor/cgmanifest.json | 2 +- .../razor/syntaxes/cshtml.tmLanguage.json | 5 +- extensions/swift/cgmanifest.json | 2 +- .../swift/syntaxes/swift.tmLanguage.json | 60 +- 13 files changed, 1631 insertions(+), 467 deletions(-) diff --git a/extensions/csharp/cgmanifest.json b/extensions/csharp/cgmanifest.json index 58a7408dbbe7..fefe63e47868 100644 --- a/extensions/csharp/cgmanifest.json +++ b/extensions/csharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "dotnet/csharp-tmLanguage", "repositoryUrl": "https://github.com/dotnet/csharp-tmLanguage", - "commitHash": "d63e2661d4e0c83b6c7810eb1d0eedc5da843b04" + "commitHash": "62026a70f9fcc42d9222eccfec34ed5ee0784f3d" } }, "license": "MIT", diff --git a/extensions/csharp/syntaxes/csharp.tmLanguage.json b/extensions/csharp/syntaxes/csharp.tmLanguage.json index 4a2497a064ab..c4d9a2519dc3 100644 --- a/extensions/csharp/syntaxes/csharp.tmLanguage.json +++ b/extensions/csharp/syntaxes/csharp.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/dotnet/csharp-tmLanguage/commit/d63e2661d4e0c83b6c7810eb1d0eedc5da843b04", + "version": "https://github.com/dotnet/csharp-tmLanguage/commit/62026a70f9fcc42d9222eccfec34ed5ee0784f3d", "name": "C#", "scopeName": "source.cs", "patterns": [ @@ -4206,7 +4206,7 @@ ] }, "invocation-expression": { - "begin": "(?x)\n(?:\n (?:(\\?)\\s*)? # preceding null-conditional operator?\n (\\.)\\s*| # preceding dot?\n (->)\\s* # preceding pointer arrow?\n)?\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # method name\n(\n <\n (?\n [^<>()]++|\n <\\g*+>|\n \\(\\g*+\\)\n )*+\n >\\s*\n)? # type arguments\n(?=\\() # open paren of argument list", + "begin": "(?x)\n(?:\n (?:(\\?)\\s*)? # preceding null-conditional operator?\n (\\.)\\s*| # preceding dot?\n (->)\\s* # preceding pointer arrow?\n)?\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # method name\n(\n <\n (?\n [^<>()]|\n \\((?:[^<>()]|<[^<>()]*>|\\([^<>()]*\\))*\\)|\n <\\g*>\n )*\n >\\s*\n)? # type arguments\n(?=\\() # open paren of argument list", "beginCaptures": { "1": { "name": "keyword.operator.null-conditional.cs" diff --git a/extensions/latex/cgmanifest.json b/extensions/latex/cgmanifest.json index 25b52bf3787e..c798b2aedd75 100644 --- a/extensions/latex/cgmanifest.json +++ b/extensions/latex/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "jlelong/vscode-latex-basics", "repositoryUrl": "https://github.com/jlelong/vscode-latex-basics", - "commitHash": "59971565a7065dbb617576c04add9d891b056319" + "commitHash": "dfa69a16a1154dbc820dc1111d72faa6954dd1e2" } }, "license": "MIT", - "version": "1.9.0", + "version": "1.10.0", "description": "The files in syntaxes/ were originally part of https://github.com/James-Yu/LaTeX-Workshop. They have been extracted in the hope that they can useful outside of the LaTeX-Workshop extension.", "licenseDetail": [ "Copyright (c) vscode-latex-basics authors", diff --git a/extensions/latex/syntaxes/LaTeX.tmLanguage.json b/extensions/latex/syntaxes/LaTeX.tmLanguage.json index e06d85538464..d7201e8c652f 100644 --- a/extensions/latex/syntaxes/LaTeX.tmLanguage.json +++ b/extensions/latex/syntaxes/LaTeX.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jlelong/vscode-latex-basics/commit/59971565a7065dbb617576c04add9d891b056319", + "version": "https://github.com/jlelong/vscode-latex-basics/commit/7a35f5e0f19b28f5f1366579e2a9ad34df4f40c9", "name": "LaTeX", "scopeName": "text.tex.latex", "patterns": [ @@ -761,6 +761,49 @@ } ] }, + { + "begin": "\\s*\\\\begin\\{(?:javacode|javaverbatim|javablock|javaconcode|javaconsole|javaconverbatim)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", + "end": "\\s*\\\\end\\{(?:javacode|javaverbatim|javablock|javaconcode|javaconsole|javaconverbatim)\\*?\\}", + "captures": { + "0": { + "patterns": [ + { + "include": "#begin-env-tokenizer" + } + ] + } + }, + "patterns": [ + { + "include": "#multiline-optional-arg-no-highlight" + }, + { + "begin": "(?:\\G|(?<=\\]))(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "(\\})", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "contentName": "variable.parameter.function.latex" + }, + { + "begin": "^(?=\\s*)", + "end": "^\\s*(?=\\\\end\\{(?:javacode|javaverbatim|javablock|javaconcode|javaconsole|javaconverbatim)\\*?\\})", + "contentName": "source.java", + "patterns": [ + { + "include": "source.java" + } + ] + } + ] + }, { "begin": "\\s*\\\\begin\\{(?:jlcode|jlverbatim|jlblock|jlconcode|jlconsole|jlconverbatim)\\*?\\}(?:\\[[a-zA-Z0-9_-]*\\])?(?=\\[|\\{|\\s*$)", "end": "\\s*\\\\end\\{(?:jlcode|jlverbatim|jlblock|jlconcode|jlconsole|jlconverbatim)\\*?\\}", @@ -1106,7 +1149,7 @@ ] }, { - "begin": "\\s*\\\\begin\\{([a-zA-Z]*code|lstlisting|minted|pyglist)\\*?\\}(?:\\[.*\\])?(?:\\{.*\\})?", + "begin": "\\s*\\\\begin\\{((?:[a-zA-Z]*code|lstlisting|minted|pyglist)\\*?)\\}(?:\\[.*\\])?(?:\\{.*\\})?", "captures": { "0": { "patterns": [ diff --git a/extensions/latex/syntaxes/cpp-grammar-bailout.tmLanguage.json b/extensions/latex/syntaxes/cpp-grammar-bailout.tmLanguage.json index 1f62f492b76f..4f70702d0bcf 100644 --- a/extensions/latex/syntaxes/cpp-grammar-bailout.tmLanguage.json +++ b/extensions/latex/syntaxes/cpp-grammar-bailout.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jlelong/vscode-latex-basics/commit/f17f354528411340e22402230be1b72e5aa1126a", + "version": "https://github.com/jlelong/vscode-latex-basics/commit/dfa69a16a1154dbc820dc1111d72faa6954dd1e2", "name": "C++", "scopeName": "source.cpp.embedded.latex", "patterns": [ @@ -20,6 +20,9 @@ { "include": "#function_definition" }, + { + "include": "#simple_array_assignment" + }, { "include": "#operator_overload" }, @@ -98,7 +101,7 @@ ], "repository": { "access_control_keywords": { - "match": "((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(((?:(?:protected)|(?:private)|(?:public)))(?:\\s+)?(:))", + "match": "((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(((?:protected|private|public))(?:\\s+)?(:))", "captures": { "1": { "patterns": [ @@ -516,10 +519,16 @@ "name": "punctuation.definition.comment.end.cpp" } }, - "name": "comment.block.cpp" + "name": "comment.block.cpp", + "patterns": [ + { + "match": "[^\\*]*\\n" + } + ], + "applyEndPatternLast": 1 }, "builtin_storage_type_initilizer": { - "begin": "\\s*+(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:(?:(\\b[a-z0-9]+\\b)|(\\b[a-zA-Z0-9]+_[a-zA-Z0-9]*\\b))|(\\b[a-z]+[A-Z][a-zA-Z0-9]*\\b))|(\\b[A-Z][A-Z_0-9]*\\b))|((?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "0": { @@ -1936,7 +2162,7 @@ ] }, "control_flow_keywords": { - "match": "((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(\\{)", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(\\{)", "end": "\\}|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "1": { - "name": "meta.qualified_type.cpp", + "name": "meta.qualified-type.cpp", "patterns": [ { "match": "::", "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, { - "match": "(?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "0": { @@ -3442,6 +3668,15 @@ { "include": "#language_constants" }, + { + "include": "#constructor_bracket_call" + }, + { + "include": "#simple_constructor_call" + }, + { + "include": "#simple_array_assignment" + }, { "include": "#builtin_storage_type_initilizer" }, @@ -3477,6 +3712,9 @@ }, { "include": "#comma" + }, + { + "include": "#unknown_variable" } ] }, @@ -3503,9 +3741,6 @@ { "include": "#preprocessor_conditional_range" }, - { - "include": "#single_line_macro" - }, { "include": "#macro" }, @@ -3524,7 +3759,7 @@ ] }, "exception_keywords": { - "match": "((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?(\\()", - "end": "\\)|(?=\\\\end\\{(?:minted|cppcode)\\})", - "beginCaptures": { - "1": { - "patterns": [ - { - "include": "#scope_resolution_function_call_inner_generated" - } - ] - }, - "2": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" - }, - "3": { - "patterns": [ - { - "include": "#template_call_range_helper" - } - ] - }, - "4": {}, - "5": { - "name": "entity.name.function.call.cpp" - }, - "6": { - "patterns": [ - { - "include": "#inline_comment" - } - ] - }, - "7": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "8": { - "name": "comment.block.cpp" - }, - "9": { - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - "10": { - "name": "meta.template.call.cpp", + "patterns": [ + { + "begin": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+)([A-Z][A-Z_0-9]*)\\b(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?(\\()", + "end": "\\)|(?=\\\\end\\{(?:minted|cppcode)\\})", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_function_call_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range_helper" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.function.call.upper-case.cpp entity.name.function.call.cpp" + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "8": { + "name": "comment.block.cpp" + }, + "9": { + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + "10": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range_helper" + } + ] + }, + "11": {}, + "12": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "13": { + "name": "comment.block.cpp" + }, + "14": { + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + "15": { + "name": "punctuation.section.arguments.begin.bracket.round.function.call.cpp punctuation.section.arguments.begin.bracket.round.function.call.upper-case.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.function.call.cpp punctuation.section.arguments.begin.bracket.round.function.call.upper-case.cpp" + } + }, "patterns": [ { - "include": "#template_call_range_helper" + "include": "#evaluation_context" } ] }, - "11": {}, - "12": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "13": { - "name": "comment.block.cpp" - }, - "14": { - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - "15": { - "name": "punctuation.section.arguments.begin.bracket.round.function.call.cpp" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.arguments.end.bracket.round.function.call.cpp" - } - }, - "patterns": [ { - "include": "#evaluation_context" + "begin": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?(\\()", + "end": "\\)|(?=\\\\end\\{(?:minted|cppcode)\\})", + "beginCaptures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_function_call_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.function.call.cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range_helper" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.function.call.cpp" + }, + "6": { + "patterns": [ + { + "include": "#inline_comment" + } + ] + }, + "7": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "8": { + "name": "comment.block.cpp" + }, + "9": { + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + "10": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range_helper" + } + ] + }, + "11": {}, + "12": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "13": { + "name": "comment.block.cpp" + }, + "14": { + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + "15": { + "name": "punctuation.section.arguments.begin.bracket.round.function.call.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.function.call.cpp" + } + }, + "patterns": [ + { + "include": "#evaluation_context" + } + ] } ] }, "function_definition": { - "begin": "(?:(?:^|\\G|(?<=;|\\}))|(?<=>|\\*\\/))\\s*+(?:((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?|\\*\\/))\\s*+(?:((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+)((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)\\b(?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "0": { @@ -3837,7 +4153,7 @@ "7": { "patterns": [ { - "match": "((?)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))", + "match": "(?<=^|\\))(?:\\s+)?(->)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))", "captures": { "1": { "name": "punctuation.definition.function.return-type.cpp" @@ -4209,14 +4525,14 @@ "name": "comment.block.cpp punctuation.definition.comment.end.cpp" }, "6": { - "name": "meta.qualified_type.cpp", + "name": "meta.qualified-type.cpp", "patterns": [ { "match": "::", "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, { - "match": "(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(\\()(\\*)(?:\\s+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:\\s+)?(?:(\\[)(\\w*)(\\])(?:\\s+)?)*(\\))(?:\\s+)?(\\()", - "end": "(\\))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?]|\\n)(?!\\()|(?=\\\\end\\{(?:minted|cppcode)\\})", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(\\()(\\*)(?:\\s+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:\\s+)?(?:(\\[)(\\w*)(\\])(?:\\s+)?)*(\\))(?:\\s+)?(\\()", + "end": "(\\))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?]|\\n)(?!\\()|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "1": { - "name": "meta.qualified_type.cpp", + "name": "meta.qualified-type.cpp", "patterns": [ { "match": "::", "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, { - "match": "(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(\\()(\\*)(?:\\s+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:\\s+)?(?:(\\[)(\\w*)(\\])(?:\\s+)?)*(\\))(?:\\s+)?(\\()", - "end": "(\\))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?]|\\n)(?!\\()|(?=\\\\end\\{(?:minted|cppcode)\\})", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(\\()(\\*)(?:\\s+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:\\s+)?(?:(\\[)(\\w*)(\\])(?:\\s+)?)*(\\))(?:\\s+)?(\\()", + "end": "(\\))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?]|\\n)(?!\\()|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "1": { - "name": "meta.qualified_type.cpp", + "name": "meta.qualified-type.cpp", "patterns": [ { "match": "::", "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, { - "match": "(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))", + "match": "(?<=protected|virtual|private|public|,|:)(?:\\s+)?(?!(?:(?:protected|private|public)|virtual))(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))", "captures": { "1": { - "name": "meta.qualified_type.cpp", + "name": "meta.qualified-type.cpp", "patterns": [ { "match": "::", "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, { - "match": "(?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\s+)?(?:(?:\\.\\*|\\.)|(?:->\\*|->))(?:\\s+)?)*)(?:\\s+)?(\\b(?!uint_least32_t[^\\w]|uint_least16_t[^\\w]|uint_least64_t[^\\w]|int_least32_t[^\\w]|int_least64_t[^\\w]|uint_fast32_t[^\\w]|uint_fast64_t[^\\w]|uint_least8_t[^\\w]|uint_fast16_t[^\\w]|int_least16_t[^\\w]|int_fast16_t[^\\w]|int_least8_t[^\\w]|uint_fast8_t[^\\w]|int_fast64_t[^\\w]|int_fast32_t[^\\w]|int_fast8_t[^\\w]|suseconds_t[^\\w]|useconds_t[^\\w]|in_addr_t[^\\w]|uintmax_t[^\\w]|uintmax_t[^\\w]|uintmax_t[^\\w]|in_port_t[^\\w]|uintptr_t[^\\w]|blksize_t[^\\w]|uint32_t[^\\w]|uint64_t[^\\w]|u_quad_t[^\\w]|intmax_t[^\\w]|intmax_t[^\\w]|unsigned[^\\w]|blkcnt_t[^\\w]|uint16_t[^\\w]|intptr_t[^\\w]|swblk_t[^\\w]|wchar_t[^\\w]|u_short[^\\w]|qaddr_t[^\\w]|caddr_t[^\\w]|daddr_t[^\\w]|fixpt_t[^\\w]|nlink_t[^\\w]|segsz_t[^\\w]|clock_t[^\\w]|ssize_t[^\\w]|int16_t[^\\w]|int32_t[^\\w]|int64_t[^\\w]|uint8_t[^\\w]|int8_t[^\\w]|mode_t[^\\w]|quad_t[^\\w]|ushort[^\\w]|u_long[^\\w]|u_char[^\\w]|double[^\\w]|signed[^\\w]|time_t[^\\w]|size_t[^\\w]|key_t[^\\w]|div_t[^\\w]|ino_t[^\\w]|uid_t[^\\w]|gid_t[^\\w]|off_t[^\\w]|pid_t[^\\w]|float[^\\w]|dev_t[^\\w]|u_int[^\\w]|short[^\\w]|bool[^\\w]|id_t[^\\w]|uint[^\\w]|long[^\\w]|char[^\\w]|void[^\\w]|auto[^\\w]|id_t[^\\w]|int[^\\w])(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b(?!\\())", + "match": "(?:((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\s+)?(?:(?:\\.\\*|\\.)|(?:->\\*|->))(?:\\s+)?)*)(?:\\s+)?(\\b(?!uint_least32_t[^\\w]|uint_least16_t[^\\w]|uint_least64_t[^\\w]|int_least32_t[^\\w]|int_least64_t[^\\w]|uint_fast32_t[^\\w]|uint_fast64_t[^\\w]|uint_least8_t[^\\w]|uint_fast16_t[^\\w]|int_least16_t[^\\w]|int_fast16_t[^\\w]|int_least8_t[^\\w]|uint_fast8_t[^\\w]|int_fast64_t[^\\w]|int_fast32_t[^\\w]|int_fast8_t[^\\w]|suseconds_t[^\\w]|useconds_t[^\\w]|in_addr_t[^\\w]|uintmax_t[^\\w]|uintmax_t[^\\w]|uintmax_t[^\\w]|in_port_t[^\\w]|uintptr_t[^\\w]|blksize_t[^\\w]|uint32_t[^\\w]|uint64_t[^\\w]|u_quad_t[^\\w]|intmax_t[^\\w]|intmax_t[^\\w]|unsigned[^\\w]|blkcnt_t[^\\w]|uint16_t[^\\w]|intptr_t[^\\w]|swblk_t[^\\w]|wchar_t[^\\w]|u_short[^\\w]|qaddr_t[^\\w]|caddr_t[^\\w]|daddr_t[^\\w]|fixpt_t[^\\w]|nlink_t[^\\w]|segsz_t[^\\w]|clock_t[^\\w]|ssize_t[^\\w]|int16_t[^\\w]|int32_t[^\\w]|int64_t[^\\w]|uint8_t[^\\w]|int8_t[^\\w]|mode_t[^\\w]|quad_t[^\\w]|ushort[^\\w]|u_long[^\\w]|u_char[^\\w]|double[^\\w]|signed[^\\w]|time_t[^\\w]|size_t[^\\w]|key_t[^\\w]|div_t[^\\w]|ino_t[^\\w]|uid_t[^\\w]|gid_t[^\\w]|off_t[^\\w]|pid_t[^\\w]|float[^\\w]|dev_t[^\\w]|u_int[^\\w]|short[^\\w]|bool[^\\w]|id_t[^\\w]|uint[^\\w]|long[^\\w]|char[^\\w]|void[^\\w]|auto[^\\w]|id_t[^\\w]|int[^\\w])(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b(?!\\())", "captures": { "1": { "patterns": [ @@ -5881,18 +6197,30 @@ "name": "variable.language.this.cpp" }, "4": { - "name": "variable.other.object.access.cpp" + "name": "variable.lower-case.cpp variable.other.object.access.$4.cpp" }, "5": { - "name": "punctuation.separator.dot-access.cpp" + "name": "variable.snake-case.cpp variable.other.object.access.$5.cpp" }, "6": { - "name": "punctuation.separator.pointer-access.cpp" + "name": "variable.camel-case.cpp variable.other.object.access.$6.cpp" }, "7": { + "name": "variable.upper-case.cpp variable.other.object.access.$7.cpp" + }, + "8": { + "name": "variable.other.unknown.$8.cpp" + }, + "9": { + "name": "punctuation.separator.dot-access.cpp" + }, + "10": { + "name": "punctuation.separator.pointer-access.cpp" + }, + "11": { "patterns": [ { - "match": "(?<=(?:\\.\\*|\\.|->|->\\*))(?:\\s+)?(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?\\*|->)))", + "match": "(?<=(?:\\.\\*|\\.|->|->\\*))(?:\\s+)?(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?\\*|->)))", "captures": { "1": { "patterns": [ @@ -5914,18 +6242,30 @@ "name": "variable.language.this.cpp" }, "6": { - "name": "variable.other.object.property.cpp" + "name": "variable.lower-case.cpp variable.other.object.property.cpp" }, "7": { - "name": "punctuation.separator.dot-access.cpp" + "name": "variable.snake-case.cpp variable.other.object.property.cpp" }, "8": { + "name": "variable.camel-case.cpp variable.other.object.property.cpp" + }, + "9": { + "name": "variable.upper-case.cpp variable.other.object.property.cpp" + }, + "10": { + "name": "variable.other.unknown.$10.cpp" + }, + "11": { + "name": "punctuation.separator.dot-access.cpp" + }, + "12": { "name": "punctuation.separator.pointer-access.cpp" } } }, { - "match": "(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?\\*|->)))", + "match": "(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?\\*|->)))", "captures": { "1": { "patterns": [ @@ -5947,12 +6287,24 @@ "name": "variable.language.this.cpp" }, "6": { - "name": "variable.other.object.access.cpp" + "name": "variable.lower-case.cpp variable.other.object.access.$6.cpp" }, "7": { - "name": "punctuation.separator.dot-access.cpp" + "name": "variable.snake-case.cpp variable.other.object.access.$7.cpp" }, "8": { + "name": "variable.camel-case.cpp variable.other.object.access.$8.cpp" + }, + "9": { + "name": "variable.upper-case.cpp variable.other.object.access.$9.cpp" + }, + "10": { + "name": "variable.other.unknown.$10.cpp" + }, + "11": { + "name": "punctuation.separator.dot-access.cpp" + }, + "12": { "name": "punctuation.separator.pointer-access.cpp" } } @@ -5965,7 +6317,7 @@ } ] }, - "8": { + "12": { "name": "variable.other.property.cpp" } } @@ -6016,7 +6368,7 @@ } }, "method_access": { - "begin": "(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\s+)?(?:(?:\\.\\*|\\.)|(?:->\\*|->))(?:\\s+)?)*)(?:\\s+)?(~?(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)(?:\\s+)?(\\()", + "begin": "(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?\\*|->)))((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*(?:\\s+)?(?:(?:\\.\\*|\\.)|(?:->\\*|->))(?:\\s+)?)*)(?:\\s+)?(~?(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)(?:\\s+)?(\\()", "end": "\\)|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "1": { @@ -6039,18 +6391,30 @@ "name": "variable.language.this.cpp" }, "6": { - "name": "variable.other.object.access.cpp" + "name": "variable.lower-case.cpp variable.other.object.access.$6.cpp" }, "7": { - "name": "punctuation.separator.dot-access.cpp" + "name": "variable.snake-case.cpp variable.other.object.access.$7.cpp" }, "8": { - "name": "punctuation.separator.pointer-access.cpp" + "name": "variable.camel-case.cpp variable.other.object.access.$8.cpp" }, "9": { + "name": "variable.upper-case.cpp variable.other.object.access.$9.cpp" + }, + "10": { + "name": "variable.other.unknown.$10.cpp" + }, + "11": { + "name": "punctuation.separator.dot-access.cpp" + }, + "12": { + "name": "punctuation.separator.pointer-access.cpp" + }, + "13": { "patterns": [ { - "match": "(?<=(?:\\.\\*|\\.|->|->\\*))(?:\\s+)?(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?\\*|->)))", + "match": "(?<=(?:\\.\\*|\\.|->|->\\*))(?:\\s+)?(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?\\*|->)))", "captures": { "1": { "patterns": [ @@ -6072,18 +6436,30 @@ "name": "variable.language.this.cpp" }, "6": { - "name": "variable.other.object.property.cpp" + "name": "variable.lower-case.cpp variable.other.object.property.cpp" }, "7": { - "name": "punctuation.separator.dot-access.cpp" + "name": "variable.snake-case.cpp variable.other.object.property.cpp" }, "8": { + "name": "variable.camel-case.cpp variable.other.object.property.cpp" + }, + "9": { + "name": "variable.upper-case.cpp variable.other.object.property.cpp" + }, + "10": { + "name": "variable.other.unknown.$10.cpp" + }, + "11": { + "name": "punctuation.separator.dot-access.cpp" + }, + "12": { "name": "punctuation.separator.pointer-access.cpp" } } }, { - "match": "(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?\\*|->)))", + "match": "(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?\\*|->)))", "captures": { "1": { "patterns": [ @@ -6105,12 +6481,24 @@ "name": "variable.language.this.cpp" }, "6": { - "name": "variable.other.object.access.cpp" + "name": "variable.lower-case.cpp variable.other.object.access.$6.cpp" }, "7": { - "name": "punctuation.separator.dot-access.cpp" + "name": "variable.snake-case.cpp variable.other.object.access.$7.cpp" }, "8": { + "name": "variable.camel-case.cpp variable.other.object.access.$8.cpp" + }, + "9": { + "name": "variable.upper-case.cpp variable.other.object.access.$9.cpp" + }, + "10": { + "name": "variable.other.unknown.$10.cpp" + }, + "11": { + "name": "punctuation.separator.dot-access.cpp" + }, + "12": { "name": "punctuation.separator.pointer-access.cpp" } } @@ -6123,10 +6511,10 @@ } ] }, - "10": { + "14": { "name": "entity.name.function.member.cpp" }, - "11": { + "15": { "name": "punctuation.section.arguments.begin.bracket.round.function.member.cpp" } }, @@ -6142,7 +6530,7 @@ ] }, "misc_keywords": { - "match": "((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(%=|\\+=|-=|\\*=|(?>=|\\|=)|(\\=))", + "begin": "^((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:((?:(?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:(?:(\\b[a-z0-9]+\\b)|(\\b[a-zA-Z0-9]+_[a-zA-Z0-9]*\\b))|(\\b[a-z]+[A-Z][a-zA-Z0-9]*\\b))|(\\b[A-Z][A-Z_0-9]*\\b))|((?>=|\\|=))|(\\=)))", "end": "(?=;)|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "1": { @@ -6446,46 +6834,49 @@ "name": "comment.block.cpp punctuation.definition.comment.end.cpp" }, "5": { + "name": "meta.assignment.cpp" + }, + "6": { "patterns": [ { "include": "#storage_specifiers" } ] }, - "6": { + "7": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "7": { + "8": { "name": "comment.block.cpp" }, - "8": { + "9": { "name": "comment.block.cpp punctuation.definition.comment.end.cpp" }, - "9": { + "10": { "patterns": [ { "include": "#inline_comment" } ] }, - "10": { + "11": { "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" }, - "11": { + "12": { "name": "comment.block.cpp" }, - "12": { + "13": { "name": "comment.block.cpp punctuation.definition.comment.end.cpp" }, - "13": { - "name": "meta.qualified_type.cpp", + "14": { + "name": "meta.qualified-type.cpp", "patterns": [ { "match": "::", "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, { - "match": "(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?=;|,)", + "begin": "^((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:((?:(?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:(?:(\\b[a-z0-9]+\\b)|(\\b[a-zA-Z0-9]+_[a-zA-Z0-9]*\\b))|(\\b[a-z]+[A-Z][a-zA-Z0-9]*\\b))|(\\b[A-Z][A-Z_0-9]*\\b))|((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)(operator)(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)(?:(?:((?:(?:delete\\[\\])|(?:delete)|(?:new\\[\\])|(?:<=>)|(?:<<=)|(?:new)|(?:>>=)|(?:\\->\\*)|(?:\\/=)|(?:%=)|(?:&=)|(?:>=)|(?:\\|=)|(?:\\+\\+)|(?:\\-\\-)|(?:\\(\\))|(?:\\[\\])|(?:\\->)|(?:\\+\\+)|(?:<<)|(?:>>)|(?:\\-\\-)|(?:<=)|(?:\\^=)|(?:==)|(?:!=)|(?:&&)|(?:\\|\\|)|(?:\\+=)|(?:\\-=)|(?:\\*=)|,|\\+|\\-|!|~|\\*|&|\\*|\\/|%|\\+|\\-|<|>|&|\\^|\\||=))|((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?((?:__cdecl|__clrcall|__stdcall|__fastcall|__thiscall|__vectorcall)?)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)(operator)(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)(?:(?:((?:delete\\[\\]|delete|new\\[\\]|<=>|<<=|new|>>=|\\->\\*|\\/=|%=|&=|>=|\\|=|\\+\\+|\\-\\-|\\(\\)|\\[\\]|\\->|\\+\\+|<<|>>|\\-\\-|<=|\\^=|==|!=|&&|\\|\\||\\+=|\\-=|\\*=|,|\\+|\\-|!|~|\\*|&|\\*|\\/|%|\\+|\\-|<|>|&|\\^|\\||=))|((?|\\?\\?>)|(?=[;>\\[\\]=]))|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "0": { @@ -7305,14 +7725,14 @@ "name": "comment.block.cpp punctuation.definition.comment.end.cpp" }, "5": { - "name": "meta.qualified_type.cpp", + "name": "meta.qualified-type.cpp", "patterns": [ { "match": "::", "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, { - "match": "(?>", + "match": "(?:<<|>>)", "name": "keyword.operator.bitwise.shift.cpp" }, { - "match": "!=|<=|>=|==|<|>", + "match": "(?:!=|<=|>=|==|<|>)", "name": "keyword.operator.comparison.cpp" }, { - "match": "&&|!|\\|\\|", + "match": "(?:&&|!|\\|\\|)", "name": "keyword.operator.logical.cpp" }, { - "match": "&|\\||\\^|~", + "match": "(?:&|\\||\\^|~)", "name": "keyword.operator.bitwise.cpp" }, { - "match": "(?:(%=|\\+=|-=|\\*=|(?>=|\\|=)|(\\=))", + "match": "(?:((?:%=|\\+=|-=|\\*=|(?>=|\\|=))|(\\=))", "captures": { "1": { "name": "keyword.operator.assignment.compound.cpp" @@ -8141,7 +8561,7 @@ } }, { - "match": "%|\\*|\\/|-|\\+", + "match": "(?:%|\\*|\\/|-|\\+)", "name": "keyword.operator.arithmetic.cpp" }, { @@ -9189,7 +9609,7 @@ "include": "#vararg_ellipses" }, { - "match": "((?:((?:(?:thread_local)|(?:volatile)|(?:register)|(?:restrict)|(?:static)|(?:extern)|(?:const)))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:\\s*+(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.])", + "match": "\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.])", "captures": { "0": { "patterns": [ @@ -11311,7 +11728,7 @@ "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, { - "match": "(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(:)(?!:)", + "match": "((?:((?:(?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(:)(?!:)", "captures": { "1": { "name": "meta.type.cpp" @@ -11530,14 +12023,14 @@ "name": "comment.block.cpp punctuation.definition.comment.end.cpp" }, "10": { - "name": "meta.qualified_type.cpp", + "name": "meta.qualified-type.cpp", "patterns": [ { "match": "::", "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, { - "match": "(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(\\[)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(,)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))*((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(\\])((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(:)(?!:)", + "match": "((?:((?:(?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(\\[)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(,)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*))*((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(\\])((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(:)(?!:)", "captures": { "1": { "name": "meta.type.cpp" @@ -11794,14 +12287,14 @@ "name": "comment.block.cpp punctuation.definition.comment.end.cpp" }, "10": { - "name": "meta.qualified_type.cpp", + "name": "meta.qualified-type.cpp", "patterns": [ { "match": "::", "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, { - "match": "(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+", - "captures": { - "0": { - "patterns": [ - { - "include": "#scope_resolution_inner_generated" - } - ] - }, + "requires_keyword": { + "begin": "((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+", - "captures": { - "0": { + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "4": { + "name": "comment.block.cpp" + }, + "5": { + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + "6": { + "name": "punctuation.section.arguments.begin.bracket.round.requires.cpp" + } + }, + "endCaptures": { + "0": { + "name": "punctuation.section.arguments.end.bracket.round.requires.cpp" + } + }, + "contentName": "meta.arguments.requires", + "patterns": [ + { + "include": "#evaluation_context" + } + ] + }, + "scope_resolution": { + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + "2": { + "patterns": [ + { + "include": "#template_call_range_helper" + } + ] + } + } + }, + "scope_resolution_function_call": { + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+", + "captures": { + "0": { "patterns": [ { "include": "#scope_resolution_function_call_inner_generated" @@ -12661,150 +13193,668 @@ } ] }, - "1": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" - }, - "2": { + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" + }, + "2": { + "patterns": [ + { + "include": "#template_call_range_helper" + } + ] + } + } + }, + "scope_resolution_template_call_inner_generated": { + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_template_call_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range_helper" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.scope-resolution.template.call.cpp" + }, + "6": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range_helper" + } + ] + }, + "7": {}, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + "11": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" + } + } + }, + "scope_resolution_template_definition": { + "match": "(::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+", + "captures": { + "0": { + "patterns": [ + { + "include": "#scope_resolution_template_definition_inner_generated" + } + ] + }, + "1": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" + }, + "2": { + "patterns": [ + { + "include": "#template_call_range_helper" + } + ] + } + } + }, + "scope_resolution_template_definition_inner_generated": { + "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?(::)", + "captures": { + "1": { + "patterns": [ + { + "include": "#scope_resolution_template_definition_inner_generated" + } + ] + }, + "2": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" + }, + "3": { + "patterns": [ + { + "include": "#template_call_range_helper" + } + ] + }, + "4": {}, + "5": { + "name": "entity.name.scope-resolution.template.definition.cpp" + }, + "6": { + "name": "meta.template.call.cpp", + "patterns": [ + { + "include": "#template_call_range_helper" + } + ] + }, + "7": {}, + "8": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "9": { + "name": "comment.block.cpp" + }, + "10": { + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + }, + "11": { + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" + } + } + }, + "semicolon": { + "match": ";", + "name": "punctuation.terminator.statement.cpp" + }, + "simple_array_assignment": { + "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:(?:(\\b[a-z0-9]+\\b)|(\\b[a-zA-Z0-9]+_[a-zA-Z0-9]*\\b))|(\\b[a-z]+[A-Z][a-zA-Z0-9]*\\b))|(\\b[A-Z][A-Z_0-9]*\\b))|((?>=|\\|=))|(\\=))", + "captures": { + "1": { + "name": "meta.qualified-type.cpp", + "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" + }, + { + "match": "(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(?=((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?=(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?(::)", - "captures": { - "1": { + }, + "6": { "patterns": [ { - "include": "#scope_resolution_template_call_inner_generated" + "match": "\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + } + } } ] }, - "2": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.call.cpp" - }, - "3": { + "7": { "patterns": [ + { + "match": "::", + "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.type.cpp" + }, + { + "match": "(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+", - "captures": { - "0": { "patterns": [ { - "include": "#scope_resolution_template_definition_inner_generated" + "match": "\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + } + } } ] }, - "1": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" - }, - "2": { + "12": {}, + "13": { "patterns": [ { - "include": "#template_call_range_helper" + "include": "#inline_comment" } ] - } - } - }, - "scope_resolution_template_definition_inner_generated": { - "match": "((::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*\\s*+)((?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?(::)", - "captures": { - "1": { + }, + "14": { "patterns": [ { - "include": "#scope_resolution_template_definition_inner_generated" + "match": "\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + } + } } ] }, - "2": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" - }, - "3": { + "15": { "patterns": [ { - "include": "#template_call_range_helper" + "include": "#inline_comment" } ] }, - "4": {}, - "5": { - "name": "entity.name.scope-resolution.template.definition.cpp" - }, - "6": { - "name": "meta.template.call.cpp", + "16": { "patterns": [ { - "include": "#template_call_range_helper" + "match": "\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+", + "captures": { + "1": { + "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" + }, + "2": { + "name": "comment.block.cpp" + }, + "3": { + "name": "comment.block.cpp punctuation.definition.comment.end.cpp" + } + } } ] - }, - "7": {}, - "8": { - "name": "comment.block.cpp punctuation.definition.comment.begin.cpp" - }, - "9": { - "name": "comment.block.cpp" - }, - "10": { - "name": "comment.block.cpp punctuation.definition.comment.end.cpp" - }, - "11": { - "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.template.definition.cpp" } } }, - "semicolon": { - "match": ";", - "name": "punctuation.terminator.statement.cpp" - }, "simple_type": { - "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:(?:unsigned)|(?:signed)|(?:short)|(?:long))|(?:(?:struct)|(?:class)|(?:union)|(?:enum)))((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?", + "match": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?", "captures": { "1": { - "name": "meta.qualified_type.cpp", + "name": "meta.qualified-type.cpp", "patterns": [ { "match": "::", "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, { - "match": "(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))|(.*(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))|(.*(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(\\()(\\*)(?:\\s+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:\\s+)?(?:(\\[)(\\w*)(\\])(?:\\s+)?)*(\\))(?:\\s+)?(\\()", - "end": "(\\))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?]|\\n)(?!\\()|(?=\\\\end\\{(?:minted|cppcode)\\})", + "begin": "(\\s*+((?:(?:(?:\\[\\[.*?\\]\\]|__attribute(?:__)?\\s*\\(\\s*\\(.*?\\)\\s*\\))|__declspec\\(.*?\\))|alignas\\(.*?\\))(?!\\)))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:unsigned|signed|short|long)|(?:struct|class|union|enum))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:((?:::)?(?:(?!\\b(?:__has_cpp_attribute|reinterpret_cast|atomic_noexcept|atomic_commit|atomic_cancel|__has_include|thread_local|dynamic_cast|synchronized|static_cast|const_cast|consteval|co_return|protected|constinit|constexpr|co_return|consteval|namespace|constexpr|constexpr|co_await|explicit|volatile|noexcept|co_yield|noexcept|noexcept|requires|typename|decltype|operator|template|continue|co_await|co_yield|volatile|register|restrict|reflexpr|mutable|alignof|include|private|defined|typedef|_Pragma|__asm__|concept|mutable|warning|default|virtual|alignas|public|sizeof|delete|not_eq|bitand|and_eq|xor_eq|typeid|switch|return|struct|static|extern|inline|friend|ifndef|define|pragma|export|import|module|catch|throw|const|or_eq|compl|while|ifdef|const|bitor|union|class|undef|error|break|using|endif|goto|line|enum|this|case|else|elif|else|not|try|for|asm|and|xor|new|do|if|or|if)\\b)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(\\()(\\*)(?:\\s+)?((?:(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)?)(?:\\s+)?(?:(\\[)(\\w*)(\\])(?:\\s+)?)*(\\))(?:\\s+)?(\\()", + "end": "(\\))((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:((?:(?:(?:\\s*+(\\/\\*)((?:[^\\*]++|\\*+(?!\\/))*+(\\*\\/))\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?]|\\n)(?!\\()|(?=\\\\end\\{(?:minted|cppcode)\\})", "beginCaptures": { "1": { - "name": "meta.qualified_type.cpp", + "name": "meta.qualified-type.cpp", "patterns": [ { "match": "::", "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, { - "match": "(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))", + "match": "(((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))", "captures": { "1": { "name": "storage.modifier.cpp" @@ -17605,14 +18627,14 @@ ] }, "6": { - "name": "meta.qualified_type.cpp", + "name": "meta.qualified-type.cpp", "patterns": [ { "match": "::", "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, { - "match": "(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(%=|\\+=|-=|\\*=|(?>=|\\|=)|(\\=))", + "match": "(?:((?:(?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:(?:(\\b[a-z0-9]+\\b)|(\\b[a-zA-Z0-9]+_[a-zA-Z0-9]*\\b))|(\\b[a-z]+[A-Z][a-zA-Z0-9]*\\b))|(\\b[A-Z][A-Z_0-9]*\\b))|((?>=|\\|=))|(\\=))", "captures": { "1": { "patterns": [ @@ -18346,14 +19388,14 @@ ] }, "5": { - "name": "meta.qualified_type.cpp", + "name": "meta.qualified-type.cpp", "patterns": [ { "match": "::", "name": "punctuation.separator.namespace.access.cpp punctuation.separator.scope-resolution.cpp" }, { - "match": "(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:(?:transaction_safe_dynamic)|(?:__has_cpp_attribute)|(?:reinterpret_cast)|(?:transaction_safe)|(?:atomic_noexcept)|(?:atomic_commit)|(?:__has_include)|(?:atomic_cancel)|(?:synchronized)|(?:thread_local)|(?:dynamic_cast)|(?:static_cast)|(?:const_cast)|(?:constexpr)|(?:co_return)|(?:constinit)|(?:namespace)|(?:protected)|(?:consteval)|(?:constexpr)|(?:constexpr)|(?:co_return)|(?:consteval)|(?:co_await)|(?:continue)|(?:template)|(?:reflexpr)|(?:volatile)|(?:register)|(?:co_await)|(?:co_yield)|(?:restrict)|(?:noexcept)|(?:volatile)|(?:override)|(?:explicit)|(?:decltype)|(?:operator)|(?:noexcept)|(?:noexcept)|(?:typename)|(?:requires)|(?:co_yield)|(?:nullptr)|(?:alignof)|(?:alignas)|(?:default)|(?:mutable)|(?:virtual)|(?:mutable)|(?:private)|(?:include)|(?:warning)|(?:_Pragma)|(?:defined)|(?:typedef)|(?:__asm__)|(?:concept)|(?:define)|(?:module)|(?:sizeof)|(?:switch)|(?:delete)|(?:pragma)|(?:and_eq)|(?:inline)|(?:xor_eq)|(?:typeid)|(?:import)|(?:extern)|(?:public)|(?:bitand)|(?:static)|(?:export)|(?:return)|(?:friend)|(?:ifndef)|(?:not_eq)|(?:false)|(?:final)|(?:break)|(?:const)|(?:catch)|(?:endif)|(?:ifdef)|(?:undef)|(?:error)|(?:audit)|(?:while)|(?:using)|(?:axiom)|(?:or_eq)|(?:compl)|(?:throw)|(?:bitor)|(?:const)|(?:line)|(?:case)|(?:else)|(?:this)|(?:true)|(?:goto)|(?:else)|(?:NULL)|(?:elif)|(?:new)|(?:asm)|(?:xor)|(?:and)|(?:try)|(?:not)|(?:for)|(?:do)|(?:if)|(?:or)|(?:if))\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))((?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?=;|,)", + "match": "(?:((?:(?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)(?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)\\s*+)?::)*+)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))?(?!(?:transaction_safe_dynamic|__has_cpp_attribute|reinterpret_cast|transaction_safe|atomic_noexcept|atomic_commit|__has_include|atomic_cancel|synchronized|thread_local|dynamic_cast|static_cast|const_cast|constexpr|co_return|constinit|namespace|protected|consteval|constexpr|constexpr|co_return|consteval|co_await|continue|template|reflexpr|volatile|register|co_await|co_yield|restrict|noexcept|volatile|override|explicit|decltype|operator|noexcept|noexcept|typename|requires|co_yield|nullptr|alignof|alignas|default|mutable|virtual|mutable|private|include|warning|_Pragma|defined|typedef|__asm__|concept|define|module|sizeof|switch|delete|pragma|and_eq|inline|xor_eq|typeid|import|extern|public|bitand|static|export|return|friend|ifndef|not_eq|false|final|break|const|catch|endif|ifdef|undef|error|audit|while|using|axiom|or_eq|compl|throw|bitor|const|line|case|else|this|true|goto|else|NULL|elif|new|asm|xor|and|try|not|for|do|if|or|if)\\b)(?:[a-zA-Z_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))(?:[a-zA-Z0-9_]|(?:\\\\u[0-9a-fA-F]{4}|\\\\U[0-9a-fA-F]{8}))*\\b((?|(?:(?:[^'\"<>\\/]|\\/[^*])++))*>)?(?![\\w<:.]))(((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))?(?:(?:&|\\*)((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z)))*(?:&|\\*))?((?:((?:\\s*+\\/\\*(?:[^\\*]++|\\*+(?!\\/))*+\\*\\/\\s*+)+)|(?:\\s++)|(?<=\\W)|(?=\\W)|^|(?:\\n?$)|\\A|\\Z))(?:(?:(?:(?:(\\b[a-z0-9]+\\b)|(\\b[a-zA-Z0-9]+_[a-zA-Z0-9]*\\b))|(\\b[a-z]+[A-Z][a-zA-Z0-9]*\\b))|(\\b[A-Z][A-Z_0-9]*\\b))|((?\n (?> # no backtracking, avoids issues with negative lookbehind at end\n (?:\n \\\\Q\n (?:(?!\\\\E)(?!/\\2).)*+\n (?:\\\\E\n # A quoted sequence may not have a closing E, in which case it extends to the end of the regex\n | (?(3)|(?\\{)?+(?\\{)?+(?\\{)?+(?\\{)?+(?\\{)?+\n .+?\n \\}(?()\\})(?()\\})(?()\\})(?()\\})(?()\\})\n (?:\\[(?!\\d)\\w+\\])?\n [X<>]?\n \\)\n | (?\\[ (?:\\\\. | [^\\[\\]] | \\g)+ \\])\n | \\(\\g?+\\)\n | (?:(?!/\\2)[^()\\[\\\\])+ # any character (until end)\n )+\n )\n)?+\n# may end with a space only if it is an extended literal or contains only a single escaped space\n(?(3)|(?(5)(?\n (?> # no backtracking, avoids issues with negative lookbehind at end\n (?:\n \\\\Q\n (?:(?!\\\\E)(?!/\\2).)*+\n (?:\\\\E\n # A quoted sequence may not have a closing E, in which case it extends to the end of the regex\n | (?(3)|(?(\\{(?:\\g<-1>|(?!{).*?)\\}))\n (?:\\[(?!\\d)\\w+\\])?\n [X<>]?\n \\)\n | (?\\[ (?:\\\\. | [^\\[\\]] | \\g)+ \\])\n | \\(\\g?+\\)\n | (?:(?!/\\2)[^()\\[\\\\])+ # any character (until end)\n )+\n )\n)?+\n# may end with a space only if it is an extended literal or contains only a single escaped space\n(?(3)|(?(5)(?'\n \"\\k'\" NamedOrNumberRef \"'\"\n '\\g<' NamedOrNumberRef '>'\n \"\\g'\" NamedOrNumberRef \"'\"", - "match": "(?x)(\\\\[gk](<)|\\\\[gk]') (?: ((?!\\d)\\w+) (?:([+-])(\\d+))? | ([+-]?\\d+) (?:([+-])(\\d+))? ) ((?(2)>|'))", + "comment": "'\\k<' NamedOrNumberRef '>'\n '\\g<' NamedOrNumberRef '>'", + "match": "(?x)(\\\\[gk]<) (?: ((?!\\d)\\w+) (?:([+-])(\\d+))? | ([+-]?\\d+) (?:([+-])(\\d+))? ) (>)", "captures": { "1": { "name": "constant.character.escape.backslash.regexp" }, - "3": { + "2": { "name": "variable.other.group-name.regexp" }, - "4": { + "3": { "name": "keyword.operator.recursion-level.regexp" }, + "4": { + "name": "constant.numeric.integer.decimal.regexp" + }, "5": { "name": "constant.numeric.integer.decimal.regexp" }, "6": { - "name": "constant.numeric.integer.decimal.regexp" + "name": "keyword.operator.recursion-level.regexp" }, "7": { - "name": "keyword.operator.recursion-level.regexp" + "name": "constant.numeric.integer.decimal.regexp" }, "8": { + "name": "constant.character.escape.backslash.regexp" + } + } + }, + { + "comment": "\"\\k'\" NamedOrNumberRef \"'\"\n \"\\g'\" NamedOrNumberRef \"'\"", + "match": "(?x)(\\\\[gk]') (?: ((?!\\d)\\w+) (?:([+-])(\\d+))? | ([+-]?\\d+) (?:([+-])(\\d+))? ) (')", + "captures": { + "1": { + "name": "constant.character.escape.backslash.regexp" + }, + "2": { + "name": "variable.other.group-name.regexp" + }, + "3": { + "name": "keyword.operator.recursion-level.regexp" + }, + "4": { "name": "constant.numeric.integer.decimal.regexp" }, - "9": { + "5": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "6": { + "name": "keyword.operator.recursion-level.regexp" + }, + "7": { + "name": "constant.numeric.integer.decimal.regexp" + }, + "8": { "name": "constant.character.escape.backslash.regexp" } } @@ -3291,7 +3321,7 @@ }, "literals-regular-expression-literal-callout": { "name": "meta.callout.regexp", - "match": "(?x)\n# PCRECallout\n(\\()(?\\?C)\n (?:\n (?\\d+)\n | `(?(?:[^`]|``)*)`\n | '(?(?:[^']|'')*)'\n | \"(?(?:[^\"]|\"\")*)\"\n | \\^(?(?:[^\\^]|\\^\\^)*)\\^\n | %(?(?:[^%]|%%)*)%\n | \\#(?(?:[^#]|\\#\\#)*)\\#\n | \\$(?(?:[^$]|\\$\\$)*)\\$\n | \\{(?(?:[^}]|\\}\\})*)\\}\n )?\n(\\))\n# NamedCallout\n| (\\()(?\\*)\n (?(?!\\d)\\w+)\n (?:\\[(?(?!\\d)\\w+)\\])?\n (?:\\{ [^,}]+ (?:,[^,}]+)* \\})?\n (\\))\n# InterpolatedCallout\n| (\\()(?\\?)\n # we only support a fixed maximum number of braces because otherwise we can't balance the number of open and close braces\n (\\{(?\\{)?+(?\\{)?+(?\\{)?+(?\\{)?+(?\\{)?+) .+? \\}(?()\\})(?()\\})(?()\\})(?()\\})(?()\\})\n (?:\\[(?(?!\\d)\\w+)\\])?\n (?[X<>]?)\n (\\))", + "match": "(?x)\n# PCRECallout\n(\\()(?\\?C)\n (?:\n (?\\d+)\n | `(?(?:[^`]|``)*)`\n | '(?(?:[^']|'')*)'\n | \"(?(?:[^\"]|\"\")*)\"\n | \\^(?(?:[^\\^]|\\^\\^)*)\\^\n | %(?(?:[^%]|%%)*)%\n | \\#(?(?:[^#]|\\#\\#)*)\\#\n | \\$(?(?:[^$]|\\$\\$)*)\\$\n | \\{(?(?:[^}]|\\}\\})*)\\}\n )?\n(\\))\n# NamedCallout\n| (\\()(?\\*)\n (?(?!\\d)\\w+)\n (?:\\[(?(?!\\d)\\w+)\\])?\n (?:\\{ [^,}]+ (?:,[^,}]+)* \\})?\n (\\))\n# InterpolatedCallout\n| (\\()(?\\?)\n (?>(\\{(?:\\g<-1>|(?!{).*?)\\}))\n (?:\\[(?(?!\\d)\\w+)\\])?\n (?[X<>]?)\n (\\))", "captures": { "1": { "name": "punctuation.definition.group.regexp" @@ -3350,13 +3380,13 @@ "19": { "name": "keyword.control.callout.regexp" }, - "26": { + "21": { "name": "variable.language.tag-name.regexp" }, - "27": { + "22": { "name": "keyword.control.callout.regexp" }, - "28": { + "23": { "name": "punctuation.definition.group.regexp" } } From 1430e1845cbf5ec29a2fc265f12c7fb5c3d685c3 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Tue, 4 Feb 2025 10:38:17 +0100 Subject: [PATCH 0340/2632] Ignoring URLs when adding line comment onEnter (#239576) removing urls from this regex --- extensions/javascript/javascript-language-configuration.json | 2 +- extensions/typescript-basics/language-configuration.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/javascript/javascript-language-configuration.json b/extensions/javascript/javascript-language-configuration.json index 46ee043c52cf..c2444c453b2b 100644 --- a/extensions/javascript/javascript-language-configuration.json +++ b/extensions/javascript/javascript-language-configuration.json @@ -231,7 +231,7 @@ // Add // when pressing enter from inside line comment { "beforeText": { - "pattern": "\/\/.*" + "pattern": "(? Date: Tue, 4 Feb 2025 11:13:07 +0100 Subject: [PATCH 0341/2632] fix #239579 (#239580) --- .../extensionManagement/node/extensionManagementService.ts | 3 +-- .../contrib/extensions/browser/extensions.contribution.ts | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index e954abf073b7..05182a5c8b94 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -53,7 +53,6 @@ import { ITelemetryService } from '../../telemetry/common/telemetry.js'; import { IUriIdentityService } from '../../uriIdentity/common/uriIdentity.js'; import { IUserDataProfilesService } from '../../userDataProfile/common/userDataProfile.js'; import { IConfigurationService } from '../../configuration/common/configuration.js'; -import { isLinux } from '../../../base/common/platform.js'; export const INativeServerExtensionManagementService = refineServiceDecorator(IExtensionManagementService); export interface INativeServerExtensionManagementService extends IExtensionManagementService { @@ -329,7 +328,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi } const { location, verificationStatus } = await this.extensionsDownloader.download(extension, operation, verifySignature, clientTargetPlatform); - if (verificationStatus !== ExtensionSignatureVerificationCode.Success && verificationStatus !== ExtensionSignatureVerificationCode.NotSigned && verifySignature && this.environmentService.isBuilt && !isLinux) { + if (verificationStatus !== ExtensionSignatureVerificationCode.Success && verificationStatus !== ExtensionSignatureVerificationCode.NotSigned && verifySignature && this.environmentService.isBuilt) { try { await this.extensionsDownloader.delete(location); } catch (e) { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index ddbfb82a80d7..0fb425ed79c8 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -69,7 +69,7 @@ import { ExtensionsCompletionItemsProvider } from './extensionsCompletionItemsPr import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js'; import { Event } from '../../../../base/common/event.js'; import { UnsupportedExtensionsMigrationContrib } from './unsupportedExtensionsMigrationContribution.js'; -import { isLinux, isNative, isWeb } from '../../../../base/common/platform.js'; +import { isNative, isWeb } from '../../../../base/common/platform.js'; import { ExtensionStorageService } from '../../../../platform/extensionManagement/common/extensionStorage.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IStringDictionary } from '../../../../base/common/collections.js'; @@ -263,7 +263,7 @@ Registry.as(ConfigurationExtensions.Configuration) description: localize('extensions.verifySignature', "When enabled, extensions are verified to be signed before getting installed."), default: true, scope: ConfigurationScope.APPLICATION, - included: isNative && !isLinux + included: isNative }, [UseUnpkgResourceApiConfigKey]: { type: 'boolean', From a54ca8f3bceecd3383334a298a250904fc83e03c Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:31:10 +0100 Subject: [PATCH 0342/2632] Remove single inline deletion representation for consistency (#239584) do not use strike through inline deletion anymore for consistency --- .../browser/view/inlineEdits/view.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts index 4799e2d5d467..ac19662118d8 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts @@ -261,7 +261,6 @@ export class InlineEditsView extends Disposable { this._useMixedLinesDiff.read(reader) === 'forStableInsertions' && this._useCodeShifting.read(reader) && isSingleLineInsertionAfterPosition(diff, edit.cursorPosition) - || isSingleLineDeletion(diff) ) ) { return 'insertionInline'; @@ -422,21 +421,6 @@ function isSingleMultiLineInsertion(diff: DetailedLineRangeMapping[]) { return true; } -function isSingleLineDeletion(diff: DetailedLineRangeMapping[]): boolean { - return diff.every(m => m.innerChanges!.every(r => isDeletion(r))); - - function isDeletion(r: RangeMapping) { - if (!r.modifiedRange.isEmpty()) { - return false; - } - const isDeletionWithinLine = r.originalRange.startLineNumber === r.originalRange.endLineNumber; - if (!isDeletionWithinLine) { - return false; - } - return true; - } -} - function growEditsToEntireWord(replacements: SingleTextEdit[], originalText: AbstractText): SingleTextEdit[] { return _growEdits(replacements, originalText, (char) => /^[a-zA-Z]$/.test(char)); } From a045859a7bd032b5e8540a5fd763847b497ab3de Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 4 Feb 2025 11:47:34 +0100 Subject: [PATCH 0343/2632] When typing a longer comment, the buttons scroll out of view (#239587) Part of #239578 --- src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts | 4 ++-- .../contrib/comments/browser/commentThreadZoneWidget.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts index a38f1639eea6..82c4eb90f567 100644 --- a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts @@ -497,9 +497,9 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { // implement in subclass } - protected _relayout(_newHeightInLines: number): void { + protected _relayout(_newHeightInLines: number, noMax?: boolean): void { const maxHeightInLines = this._getMaximumHeightInLines(); - const newHeightInLines = maxHeightInLines === undefined ? _newHeightInLines : Math.min(maxHeightInLines, _newHeightInLines); + const newHeightInLines = (!noMax && (maxHeightInLines !== undefined)) ? Math.min(maxHeightInLines, _newHeightInLines) : _newHeightInLines; if (this._viewZone && this._viewZone.heightInLines !== newHeightInLines) { this.editor.changeViewZones(accessor => { if (this._viewZone) { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 0039a4030eaa..d6c4d858d5c7 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -502,7 +502,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } const capture = StableEditorScrollState.capture(this.editor); - this._relayout(computedLinesNumber); + this._relayout(computedLinesNumber, true); capture.restore(this.editor); } } From 1ff1b71e4b5c86cbd0188ce85a7ec927706dc2a9 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:49:54 +0100 Subject: [PATCH 0344/2632] Remove collapsing inline completions functionality (#239588) remove collapsing inline completions --- .../controller/inlineCompletionsController.ts | 2 -- .../browser/model/inlineCompletionsModel.ts | 14 +------------- .../inlineCompletions/browser/model/inlineEdit.ts | 2 -- .../browser/view/inlineEdits/view.ts | 7 +------ .../view/inlineEdits/viewAndDiffProducer.ts | 4 +--- 5 files changed, 3 insertions(+), 26 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts index 5b9811a2f036..fd94296cd562 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController.ts @@ -191,8 +191,6 @@ export class InlineCompletionsController extends Disposable { if (!m) { return; } if (m.state.get()?.kind === 'ghostText') { this.model.get()?.stop(); - } else if (m.state.get()?.inlineCompletion) { - this.model.get()?.collapseInlineEdit(); } } })); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts index d701eedd8120..e1582bea38c7 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts @@ -261,14 +261,6 @@ export class InlineCompletionsModel extends Disposable { }); } - private readonly _collapsedInlineEditId = observableValue(this, undefined); - - public collapseInlineEdit(): void { - const currentInlineEdit = this.inlineEditState.get()?.inlineCompletion; - if (!currentInlineEdit) { return; } - this._collapsedInlineEditId.set(currentInlineEdit.semanticId, undefined); - } - private readonly _inlineCompletionItems = derivedOpts({ owner: this }, reader => { const c = this._source.inlineCompletions.read(reader); if (!c) { return undefined; } @@ -379,13 +371,9 @@ export class InlineCompletionsModel extends Disposable { return undefined; } - const cursorDist = LineRange.fromRange(edit.range).distanceToLine(this.primaryPosition.read(reader).lineNumber); - const disableCollapsing = true; - const currentItemIsCollapsed = !disableCollapsing && (cursorDist > 1 && this._collapsedInlineEditId.read(reader) === inlineEditResult.semanticId); - const commands = inlineEditResult.inlineCompletion.source.inlineCompletions.commands; const renderExplicitly = this._jumpedTo.read(reader); - const inlineEdit = new InlineEdit(edit, currentItemIsCollapsed, renderExplicitly, commands ?? [], inlineEditResult.inlineCompletion); + const inlineEdit = new InlineEdit(edit, renderExplicitly, commands ?? [], inlineEditResult.inlineCompletion); return { kind: 'inlineEdit', inlineEdit, inlineCompletion: inlineEditResult, edits: [edit], cursorAtInlineEdit }; } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEdit.ts b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEdit.ts index d87d1c26d9b3..63e45b69ac7d 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEdit.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/model/inlineEdit.ts @@ -10,7 +10,6 @@ import { InlineCompletionItem } from './provideInlineCompletions.js'; export class InlineEdit { constructor( public readonly edit: SingleTextEdit, - public readonly isCollapsed: boolean, public readonly renderExplicitly: boolean, public readonly commands: readonly Command[], public readonly inlineCompletion: InlineCompletionItem, @@ -26,7 +25,6 @@ export class InlineEdit { public equals(other: InlineEdit): boolean { return this.edit.equals(other.edit) - && this.isCollapsed === other.isCollapsed && this.renderExplicitly === other.renderExplicitly && this.inlineCompletion === other.inlineCompletion; } diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts index ac19662118d8..d55b24fb5704 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts @@ -157,7 +157,7 @@ export class InlineEditsView extends Disposable { return { modifiedText: new StringText(e.newText), diff: e.diff, - mode: e.state.kind === 'collapsed' ? 'sideBySide' : e.state.kind, + mode: e.state.kind, modifiedCodeEditor: this._sideBySide.previewEditor, }; }); @@ -250,10 +250,6 @@ export class InlineEditsView extends Disposable { // Determine the view based on the edit / diff - if (edit.isCollapsed) { - return 'collapsed'; - } - const inner = diff.flatMap(d => d.innerChanges ?? []); const isSingleInnerEdit = inner.length === 1; if ( @@ -317,7 +313,6 @@ export class InlineEditsView extends Disposable { this._previousView = { id: edit.inlineCompletion.id, view, userJumpedToIt: edit.userJumpedToIt, editorWidth: this._editor.getLayoutInfo().width }; switch (view) { - case 'collapsed': return { kind: 'collapsed' as const }; case 'insertionInline': return { kind: 'insertionInline' as const }; case 'mixedLines': return { kind: 'mixedLines' as const }; case 'interleavedLines': return { kind: 'interleavedLines' as const }; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/viewAndDiffProducer.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/viewAndDiffProducer.ts index 11c86a03b3f0..81613dc0ad49 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/viewAndDiffProducer.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/viewAndDiffProducer.ts @@ -44,7 +44,7 @@ export class InlineEditsViewAndDiffProducer extends Disposable { // TODO: This c const diffEdits = new TextEdit(edits); const text = new TextModelText(textModel); - return new InlineEditWithChanges(text, diffEdits, inlineEdit.isCollapsed, model.primaryPosition.get(), inlineEdit.renderExplicitly, inlineEdit.commands, inlineEdit.inlineCompletion); + return new InlineEditWithChanges(text, diffEdits, model.primaryPosition.get(), inlineEdit.renderExplicitly, inlineEdit.commands, inlineEdit.inlineCompletion); }); constructor( @@ -69,7 +69,6 @@ export class InlineEditWithChanges { constructor( public readonly originalText: AbstractText, public readonly edit: TextEdit, - public readonly isCollapsed: boolean, public readonly cursorPosition: Position, public readonly userJumpedToIt: boolean, public readonly commands: readonly Command[], @@ -80,7 +79,6 @@ export class InlineEditWithChanges { equals(other: InlineEditWithChanges) { return this.originalText.getValue() === other.originalText.getValue() && this.edit.equals(other.edit) && - this.isCollapsed === other.isCollapsed && this.cursorPosition.equals(other.cursorPosition) && this.userJumpedToIt === other.userJumpedToIt && this.commands === other.commands && From d0b83e6142c35e8635c86f86c117d15fd8748068 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 4 Feb 2025 13:01:11 +0100 Subject: [PATCH 0345/2632] support `InlineChatRunOptions` for inline chat (#239591) --- .../inlineChat/browser/inlineChatActions.ts | 14 ++++-- .../browser/inlineChatController.ts | 5 +- .../browser/inlineChatController2.ts | 46 +++++++++++++++---- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 561bef81e76d..ba75016bb02e 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 '../../../../editor/browser/widget/diff import { EmbeddedCodeEditorWidget } from '../../../../editor/browser/widget/codeEditor/embeddedCodeEditorWidget.js'; import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; import { InlineChatController, InlineChatRunOptions } from './inlineChatController.js'; -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, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, 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, CTX_INLINE_CHAT_POSSIBLE, ACTION_START } from '../common/inlineChat.js'; +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, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatResponseType, ACTION_REGENERATE_RESPONSE, 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, CTX_INLINE_CHAT_POSSIBLE, ACTION_START, CTX_INLINE_CHAT_HAS_AGENT2 } from '../common/inlineChat.js'; import { localize, localize2 } from '../../../../nls.js'; import { Action2, IAction2Options, MenuId } from '../../../../platform/actions/common/actions.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; @@ -20,7 +20,7 @@ import { KeybindingWeight } from '../../../../platform/keybinding/common/keybind import { IEditorService } from '../../../services/editor/common/editorService.js'; import { ICodeEditorService } from '../../../../editor/browser/services/codeEditorService.js'; import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from '../../../../platform/accessibility/common/accessibility.js'; -import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; +import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { IPreferencesService } from '../../../services/preferences/common/preferences.js'; import { ILogService } from '../../../../platform/log/common/log.js'; @@ -72,7 +72,10 @@ export class StartSessionAction extends Action2 { } }); } - override run(accessor: ServicesAccessor, ...args: any[]): void { + override run(accessor: ServicesAccessor, ...args: any[]): any { + + const commandService = accessor.get(ICommandService); + const contextKeyService = accessor.get(IContextKeyService); const codeEditorService = accessor.get(ICodeEditorService); const editor = codeEditorService.getActiveCodeEditor(); if (!editor || editor.isSimpleWidget) { @@ -80,6 +83,10 @@ export class StartSessionAction extends Action2 { return; } + if (contextKeyService.contextMatchesRules(CTX_INLINE_CHAT_HAS_AGENT2)) { + return commandService.executeCommand('inlineChat2.start', ...args); + } + // precondition does hold return editor.invokeWithinContext((editorAccessor) => { const kbService = editorAccessor.get(IContextKeyService); @@ -134,7 +141,6 @@ export class UnstashSessionAction extends EditorAction2 { if (session) { ctrl.run({ existingSession: session, - isUnstashed: true }); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 6ca0b87ae9d1..3ef89c1c8f11 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -78,10 +78,7 @@ export abstract class InlineChatRunOptions { message?: string; autoSend?: boolean; existingSession?: Session; - isUnstashed?: boolean; position?: IPosition; - withIntentDetection?: boolean; - headless?: boolean; static isInlineChatRunOptions(options: any): options is InlineChatRunOptions { const { initialSelection, initialRange, message, autoSend, position, existingSession } = options; @@ -314,7 +311,7 @@ export class InlineChatController implements IEditorContribution { delete options.position; } - const widgetPosition = this._showWidget(options.headless ?? session?.headless, true, initPosition); + const widgetPosition = this._showWidget(session?.headless, true, initPosition); // this._updatePlaceholder(); let errorMessage = localize('create.fail', "Failed to start editor chat"); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts index b735b3edcc0e..f07c82bc1548 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts @@ -33,7 +33,9 @@ import { ChatAgentLocation } from '../../chat/common/chatAgents.js'; import { WorkingSetEntryState } from '../../chat/common/chatEditingService.js'; import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js'; import { CTX_INLINE_CHAT_HAS_AGENT2, CTX_INLINE_CHAT_POSSIBLE, CTX_INLINE_CHAT_VISIBLE } from '../common/inlineChat.js'; +import { InlineChatRunOptions } from './inlineChatController.js'; import { IInlineChatSession2, IInlineChatSessionService } from './inlineChatSessionService.js'; +import { EditorBasedInlineChatWidget } from './inlineChatWidget.js'; import { InlineChatZoneWidget } from './inlineChatZoneWidget.js'; @@ -49,11 +51,13 @@ export class InlineChatController2 implements IEditorContribution { } private readonly _store = new DisposableStore(); - - private readonly _showWidgetOverrideObs = observableValue(this, false); private readonly _isActiveController = observableValue(this, false); + private readonly _zone: Lazy; + get widget(): EditorBasedInlineChatWidget { + return this._zone.value.widget; + } constructor( private readonly _editor: ICodeEditor, @@ -66,7 +70,7 @@ export class InlineChatController2 implements IEditorContribution { const ctxHasSession = CTX_HAS_SESSION.bindTo(contextKeyService); const ctxInlineChatVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService); - const zone = new Lazy(() => { + this._zone = new Lazy(() => { const location: IChatWidgetLocationOptions = { @@ -176,17 +180,17 @@ export class InlineChatController2 implements IEditorContribution { const session = visibleSessionObs.read(r); if (!session) { - zone.rawValue?.hide(); + this._zone.rawValue?.hide(); _editor.focus(); ctxInlineChatVisible.reset(); } else { ctxInlineChatVisible.set(true); - zone.value.widget.setChatModel(session.chatModel); - if (!zone.value.position) { - zone.value.show(session.initialPosition); + this._zone.value.widget.setChatModel(session.chatModel); + if (!this._zone.value.position) { + this._zone.value.show(session.initialPosition); } - zone.value.reveal(zone.value.position!); - zone.value.widget.focus(); + this._zone.value.reveal(this._zone.value.position!); + this._zone.value.widget.focus(); session.editingSession.getEntry(session.uri)?.autoAcceptController.get()?.cancel(); } })); @@ -264,9 +268,31 @@ export class StartSessionAction2 extends EditorAction2 { if (!editor.hasModel()) { return; } + const ctrl = InlineChatController2.get(editor); + if (!ctrl) { + return; + } + const textModel = editor.getModel(); await inlineChatSessions.createSession2(editor, textModel.uri, CancellationToken.None); - InlineChatController2.get(editor)?.markActiveController(); + + ctrl.markActiveController(); + + const arg = args[0]; + if (arg && InlineChatRunOptions.isInlineChatRunOptions(arg)) { + if (arg.initialRange) { + editor.revealRange(arg.initialRange); + } + if (arg.initialSelection) { + editor.setSelection(arg.initialSelection); + } + if (arg.message) { + ctrl.widget.chatWidget.setInput(arg.message); + if (arg.autoSend) { + await ctrl.widget.chatWidget.acceptInput(); + } + } + } } } From 4edc46abe1e05f0686eb8b0b4150a75c8eb1b9fb Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 4 Feb 2025 13:21:12 +0100 Subject: [PATCH 0346/2632] fix #239574 (#239594) --- src/vs/platform/request/node/requestService.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index 333705f23e22..7a381e4ec40f 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -201,8 +201,23 @@ export async function nodeRequest(options: NodeRequestOptions, token: Cancellati req.on('error', reject); + // Handle timeout if (options.timeout) { - req.setTimeout(options.timeout); + // Chromium network requests do not support the `timeout` option + if (options.isChromiumNetwork) { + // Use Node's setTimeout for Chromium network requests + const timeout = setTimeout(() => { + req.abort(); + reject(new Error(`Request timeout after ${options.timeout}ms`)); + }, options.timeout); + + // Clear timeout when request completes + req.on('response', () => clearTimeout(timeout)); + req.on('error', () => clearTimeout(timeout)); + req.on('abort', () => clearTimeout(timeout)); + } else { + req.setTimeout(options.timeout); + } } // Chromium will abort the request if forbidden headers are set. From 05582f9dd1ab81ca03be0863cd3e9af15cc00ad7 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 4 Feb 2025 13:22:33 +0100 Subject: [PATCH 0347/2632] fix #227446 (#239595) --- .../extensionManagement/common/extensionGalleryService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 7975c97c5126..90b110c251ae 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -1569,6 +1569,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const context = await this.requestService.request({ type: 'GET', url: this.extensionsControlUrl, + timeout: 10000 /*10s*/ }, CancellationToken.None); if (context.res.statusCode !== 200) { From 9cf5e81d4f5299c7c88dcf540c5e440ab5f52842 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:03:18 +0100 Subject: [PATCH 0348/2632] Git - switch to using diff information for the stage/unstage/revert selected ranges commands (#239597) --- extensions/git/src/commands.ts | 55 ++++++++++--------- extensions/git/src/staging.ts | 7 +++ ...de.proposed.textEditorDiffInformation.d.ts | 15 +++-- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 47b1f087b94b..b7a1a85bb4f7 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -12,7 +12,7 @@ import { ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, Remo import { Git, Stash } from './git'; import { Model } from './model'; import { GitResourceGroup, Repository, Resource, ResourceGroupType } from './repository'; -import { DiffEditorSelectionHunkToolbarContext, applyLineChanges, getModifiedRange, getWorkingTreeAndIndexDiffInformation, getWorkingTreeDiffInformation, intersectDiffWithRange, invertLineChange, toLineChanges, toLineRanges } from './staging'; +import { DiffEditorSelectionHunkToolbarContext, applyLineChanges, getIndexDiffInformation, getModifiedRange, getWorkingTreeDiffInformation, intersectDiffWithRange, invertLineChange, toLineChanges, toLineRanges } from './staging'; import { fromGitUri, toGitUri, isGitUri, toMergeUris, toMultiFileDiffEditorUris } from './uri'; import { dispose, getCommitShortHash, grep, isDefined, isDescendant, pathEquals, relativePath, truncate } from './util'; import { GitTimelineItem } from './timelineProvider'; @@ -1567,17 +1567,20 @@ export class CommandCenter { return; } - this.logger.trace(`[CommandCenter][stageSelectedChanges] changes: ${JSON.stringify(changes)}`); - const workingTreeDiffInformation = getWorkingTreeDiffInformation(textEditor); - if (workingTreeDiffInformation) { - this.logger.trace(`[CommandCenter][stageSelectedChanges] diffInformation: ${JSON.stringify(workingTreeDiffInformation)}`); - this.logger.trace(`[CommandCenter][stageSelectedChanges] diffInformation changes: ${JSON.stringify(toLineChanges(workingTreeDiffInformation))}`); + if (!workingTreeDiffInformation) { + return; } + const workingTreeLineChanges = toLineChanges(workingTreeDiffInformation); + + this.logger.trace(`[CommandCenter][stageSelectedChanges] changes: ${JSON.stringify(changes)}`); + this.logger.trace(`[CommandCenter][stageSelectedChanges] diffInformation: ${JSON.stringify(workingTreeDiffInformation)}`); + this.logger.trace(`[CommandCenter][stageSelectedChanges] diffInformation changes: ${JSON.stringify(workingTreeLineChanges)}`); + const modifiedDocument = textEditor.document; const selectedLines = toLineRanges(textEditor.selections, modifiedDocument); - const selectedChanges = changes + const selectedChanges = workingTreeLineChanges .map(change => selectedLines.reduce((result, range) => result || intersectDiffWithRange(modifiedDocument, change, range), null)) .filter(d => !!d) as LineChange[]; @@ -1759,22 +1762,25 @@ export class CommandCenter { return; } - this.logger.trace(`[CommandCenter][revertSelectedRanges] changes: ${JSON.stringify(changes)}`); - const workingTreeDiffInformation = getWorkingTreeDiffInformation(textEditor); - if (workingTreeDiffInformation) { - this.logger.trace(`[CommandCenter][revertSelectedRanges] diffInformation: ${JSON.stringify(workingTreeDiffInformation)}`); - this.logger.trace(`[CommandCenter][revertSelectedRanges] diffInformation changes: ${JSON.stringify(toLineChanges(workingTreeDiffInformation))}`); + if (!workingTreeDiffInformation) { + return; } + const workingTreeLineChanges = toLineChanges(workingTreeDiffInformation); + + this.logger.trace(`[CommandCenter][revertSelectedRanges] changes: ${JSON.stringify(changes)}`); + this.logger.trace(`[CommandCenter][revertSelectedRanges] diffInformation: ${JSON.stringify(workingTreeDiffInformation)}`); + this.logger.trace(`[CommandCenter][revertSelectedRanges] diffInformation changes: ${JSON.stringify(workingTreeLineChanges)}`); + const modifiedDocument = textEditor.document; const selections = textEditor.selections; - const selectedChanges = changes.filter(change => { + const selectedChanges = workingTreeLineChanges.filter(change => { const modifiedRange = getModifiedRange(modifiedDocument, change); return selections.every(selection => !selection.intersection(modifiedRange)); }); - if (selectedChanges.length === changes.length) { + if (selectedChanges.length === workingTreeLineChanges.length) { window.showInformationMessage(l10n.t('The selection range does not contain any changes.')); return; } @@ -1859,24 +1865,21 @@ export class CommandCenter { return; } - this.logger.trace(`[CommandCenter][unstageSelectedRanges] changes: ${JSON.stringify(changes)}`); - - const workingTreeDiffInformation = getWorkingTreeDiffInformation(textEditor); - if (workingTreeDiffInformation) { - this.logger.trace(`[CommandCenter][unstageSelectedRanges] diffInformation (working tree): ${JSON.stringify(workingTreeDiffInformation)}`); - this.logger.trace(`[CommandCenter][unstageSelectedRanges] diffInformation changes (working tree): ${JSON.stringify(toLineChanges(workingTreeDiffInformation))}`); + const indexDiffInformation = getIndexDiffInformation(textEditor); + if (!indexDiffInformation) { + return; } - const workingTreeAndIndexDiffInformation = getWorkingTreeAndIndexDiffInformation(textEditor); - if (workingTreeAndIndexDiffInformation) { - this.logger.trace(`[CommandCenter][unstageSelectedRanges] diffInformation (working tree + index): ${JSON.stringify(workingTreeAndIndexDiffInformation)}`); - this.logger.trace(`[CommandCenter][unstageSelectedRanges] diffInformation changes (working tree + index): ${JSON.stringify(toLineChanges(workingTreeAndIndexDiffInformation))}`); - } + const indexLineChanges = toLineChanges(indexDiffInformation); + + this.logger.trace(`[CommandCenter][unstageSelectedRanges] changes: ${JSON.stringify(changes)}`); + this.logger.trace(`[CommandCenter][unstageSelectedRanges] diffInformation: ${JSON.stringify(indexDiffInformation)}`); + this.logger.trace(`[CommandCenter][unstageSelectedRanges] diffInformation changes: ${JSON.stringify(indexLineChanges)}`); const originalUri = toGitUri(modifiedUri, 'HEAD'); const originalDocument = await workspace.openTextDocument(originalUri); const selectedLines = toLineRanges(textEditor.selections, modifiedDocument); - const selectedDiffs = changes + const selectedDiffs = indexLineChanges .map(change => selectedLines.reduce((result, range) => result || intersectDiffWithRange(modifiedDocument, change, range), null)) .filter(c => !!c) as LineChange[]; diff --git a/extensions/git/src/staging.ts b/extensions/git/src/staging.ts index 38a462aedf62..14e4e379e478 100644 --- a/extensions/git/src/staging.ts +++ b/extensions/git/src/staging.ts @@ -178,6 +178,13 @@ export function toLineChanges(diffInformation: TextEditorDiffInformation): LineC }); } +export function getIndexDiffInformation(textEditor: TextEditor): TextEditorDiffInformation | undefined { + // Diff Editor (Index) + return textEditor.diffInformation?.find(diff => + diff.original && isGitUri(diff.original) && fromGitUri(diff.original).ref === 'HEAD' && + diff.modified && isGitUri(diff.modified) && fromGitUri(diff.modified).ref === ''); +} + export function getWorkingTreeDiffInformation(textEditor: TextEditor): TextEditorDiffInformation | undefined { // Working tree diff information. Diff Editor (Working Tree) -> Text Editor return getDiffInformation(textEditor, '~') ?? getDiffInformation(textEditor, ''); diff --git a/src/vscode-dts/vscode.proposed.textEditorDiffInformation.d.ts b/src/vscode-dts/vscode.proposed.textEditorDiffInformation.d.ts index b86d377c7660..faa18c2932d6 100644 --- a/src/vscode-dts/vscode.proposed.textEditorDiffInformation.d.ts +++ b/src/vscode-dts/vscode.proposed.textEditorDiffInformation.d.ts @@ -12,15 +12,14 @@ declare module 'vscode' { Modification = 3 } + export interface TextEditorLineRange { + readonly startLineNumber: number; + readonly endLineNumberExclusive: number; + } + export interface TextEditorChange { - readonly original: { - readonly startLineNumber: number; - readonly endLineNumberExclusive: number; - }; - readonly modified: { - readonly startLineNumber: number; - readonly endLineNumberExclusive: number; - }; + readonly original: TextEditorLineRange; + readonly modified: TextEditorLineRange; readonly kind: TextEditorChangeKind; } From d35f9482c008bdd1cb34a42c2281cc166c8fc661 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Feb 2025 06:23:06 -0800 Subject: [PATCH 0349/2632] Move tests fig spec tests into folder per spec Part of #239515 --- .../src/test/completions/cd.test.ts | 42 +++++ .../test/completions/code-insiders.test.ts | 15 ++ .../src/test/completions/code.test.ts | 58 +++++++ .../terminal-suggest/src/test/helpers.ts | 38 +++++ .../src/test/terminalSuggestMain.test.ts | 143 +++--------------- 5 files changed, 174 insertions(+), 122 deletions(-) create mode 100644 extensions/terminal-suggest/src/test/completions/cd.test.ts create mode 100644 extensions/terminal-suggest/src/test/completions/code-insiders.test.ts create mode 100644 extensions/terminal-suggest/src/test/completions/code.test.ts create mode 100644 extensions/terminal-suggest/src/test/helpers.ts diff --git a/extensions/terminal-suggest/src/test/completions/cd.test.ts b/extensions/terminal-suggest/src/test/completions/cd.test.ts new file mode 100644 index 000000000000..1c669559d592 --- /dev/null +++ b/extensions/terminal-suggest/src/test/completions/cd.test.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import cdSpec from '../../completions/cd'; +import { testPaths, type ISuiteSpec } from '../helpers'; + +export const cdTestSuiteSpec: ISuiteSpec = { + name: 'cd', + completionSpecs: cdSpec, + availableCommands: 'cd', + testSpecs: [ + // Typing a path + { input: '.|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: './|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: './.|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Typing the command + { input: 'c|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'cd|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Basic arguments + { input: 'cd |', expectedCompletions: ['~', '-'], expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + { input: 'cd -|', expectedCompletions: ['-'], expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + { input: 'cd ~|', expectedCompletions: ['~'], expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + + // Relative paths + { input: 'cd c|', expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + { input: 'cd child|', expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + { input: 'cd .|', expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + { input: 'cd ./|', expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + { input: 'cd ./child|', expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + { input: 'cd ..|', expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + + // Relative directories (changes cwd due to /) + { input: 'cd child/|', expectedResourceRequests: { type: 'folders', cwd: testPaths.cwdChild } }, + { input: 'cd ../|', expectedResourceRequests: { type: 'folders', cwd: testPaths.cwdParent } }, + { input: 'cd ../sibling|', expectedResourceRequests: { type: 'folders', cwd: testPaths.cwdParent } }, + ] +}; diff --git a/extensions/terminal-suggest/src/test/completions/code-insiders.test.ts b/extensions/terminal-suggest/src/test/completions/code-insiders.test.ts new file mode 100644 index 000000000000..447772ad8b7f --- /dev/null +++ b/extensions/terminal-suggest/src/test/completions/code-insiders.test.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import codeInsidersCompletionSpec from '../../completions/code-insiders'; +import type { ISuiteSpec } from '../helpers'; +import { createCodeTestSpecs } from './code.test'; + +export const codeInsidersTestSuite: ISuiteSpec = { + name: 'code-insiders', + completionSpecs: codeInsidersCompletionSpec, + availableCommands: 'code-insiders', + testSpecs: createCodeTestSpecs('code-insiders') +}; diff --git a/extensions/terminal-suggest/src/test/completions/code.test.ts b/extensions/terminal-suggest/src/test/completions/code.test.ts new file mode 100644 index 000000000000..f65cd2e4d5d6 --- /dev/null +++ b/extensions/terminal-suggest/src/test/completions/code.test.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import codeCompletionSpec from '../../completions/code'; +import { testPaths, type ISuiteSpec, type ITestSpec } from '../helpers'; + +export function createCodeTestSpecs(executable: string): ITestSpec[] { + const codeOptions = ['-', '--add', '--category', '--diff', '--disable-extension', '--disable-extensions', '--disable-gpu', '--enable-proposed-api', '--extensions-dir', '--goto', '--help', '--inspect-brk-extensions', '--inspect-extensions', '--install-extension', '--list-extensions', '--locale', '--log', '--max-memory', '--merge', '--new-window', '--pre-release', '--prof-startup', '--profile', '--reuse-window', '--show-versions', '--status', '--sync', '--telemetry', '--uninstall-extension', '--user-data-dir', '--verbose', '--version', '--wait', '-a', '-d', '-g', '-h', '-m', '-n', '-r', '-s', '-v', '-w']; + const localeOptions = ['bg', 'de', 'en', 'es', 'fr', 'hu', 'it', 'ja', 'ko', 'pt-br', 'ru', 'tr', 'zh-CN', 'zh-TW']; + const categoryOptions = ['azure', 'data science', 'debuggers', 'extension packs', 'education', 'formatters', 'keymaps', 'language packs', 'linters', 'machine learning', 'notebooks', 'programming languages', 'scm providers', 'snippets', 'testing', 'themes', 'visualization', 'other']; + const logOptions = ['critical', 'error', 'warn', 'info', 'debug', 'trace', 'off']; + const syncOptions = ['on', 'off']; + + const typingTests: ITestSpec[] = []; + for (let i = 1; i < executable.length; i++) { + const input = `${executable.slice(0, i)}|`; + typingTests.push({ input, expectedCompletions: [executable], expectedResourceRequests: input.endsWith(' ') ? undefined : { type: 'both', cwd: testPaths.cwd } }); + } + + return [ + // Typing the command + ...typingTests, + + // Basic arguments + { input: `${executable} |`, expectedCompletions: codeOptions }, + { input: `${executable} --locale |`, expectedCompletions: localeOptions }, + { input: `${executable} --diff |`, expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } }, + { input: `${executable} --diff ./file1 |`, expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } }, + { input: `${executable} --merge |`, expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } }, + { input: `${executable} --merge ./file1 ./file2 |`, expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } }, + { input: `${executable} --merge ./file1 ./file2 ./base |`, expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } }, + { input: `${executable} --goto |`, expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } }, + { input: `${executable} --user-data-dir |`, expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + { input: `${executable} --profile |`, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: `${executable} --install-extension |`, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: `${executable} --uninstall-extension |`, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: `${executable} --log |`, expectedCompletions: logOptions }, + { input: `${executable} --sync |`, expectedCompletions: syncOptions }, + { input: `${executable} --extensions-dir |`, expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + { input: `${executable} --list-extensions |`, expectedCompletions: codeOptions }, + { input: `${executable} --show-versions |`, expectedCompletions: codeOptions }, + { input: `${executable} --category |`, expectedCompletions: categoryOptions }, + { input: `${executable} --category a|`, expectedCompletions: categoryOptions.filter(c => c.startsWith('a')) }, + + // Middle of command + { input: `${executable} | --locale`, expectedCompletions: codeOptions }, + ]; +} + +export const codeTestSuite: ISuiteSpec = { + name: 'code', + completionSpecs: codeCompletionSpec, + availableCommands: 'code', + testSpecs: createCodeTestSpecs('code') +}; diff --git a/extensions/terminal-suggest/src/test/helpers.ts b/extensions/terminal-suggest/src/test/helpers.ts new file mode 100644 index 000000000000..3075022d541f --- /dev/null +++ b/extensions/terminal-suggest/src/test/helpers.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import * as vscode from 'vscode'; +import type { Uri } from 'vscode'; + +export interface ISuiteSpec { + name: string; + completionSpecs: Fig.Spec | Fig.Spec[]; + // TODO: This seems unnecessary, ideally getCompletionItemsFromSpecs would only consider the + // spec's completions + availableCommands: string | string[]; + testSpecs: ITestSpec[]; +} + +export interface ITestSpec { + input: string; + expectedResourceRequests?: { + type: 'files' | 'folders' | 'both'; + cwd: Uri; + }; + expectedCompletions?: string[]; +} + +const fixtureDir = vscode.Uri.joinPath(vscode.Uri.file(__dirname), '../../testWorkspace'); + +/** + * A default set of paths shared across tests. + */ +export const testPaths = { + fixtureDir, + cwdParent: vscode.Uri.joinPath(fixtureDir, 'parent'), + cwd: vscode.Uri.joinPath(fixtureDir, 'parent/home'), + cwdChild: vscode.Uri.joinPath(fixtureDir, 'parent/home/child'), +}; diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index 68a2abbb2566..c84693144eee 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -3,82 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as vscode from 'vscode'; import { deepStrictEqual, strictEqual } from 'assert'; import 'mocha'; -import { asArray, getCompletionItemsFromSpecs } from '../terminalSuggestMain'; -import cdSpec from '../completions/cd'; -import codeCompletionSpec from '../completions/code'; -import codeInsidersCompletionSpec from '../completions/code-insiders'; -import type { Uri } from 'vscode'; import { basename } from 'path'; +import { asArray, getCompletionItemsFromSpecs } from '../terminalSuggestMain'; import { getTokenType } from '../tokens'; - -const fixtureDir = vscode.Uri.joinPath(vscode.Uri.file(__dirname), '../../testWorkspace'); -const testCwdParent = vscode.Uri.joinPath(fixtureDir, 'parent'); -const testCwd = vscode.Uri.joinPath(fixtureDir, 'parent/home'); -const testCwdChild = vscode.Uri.joinPath(fixtureDir, 'parent/home/child'); - -interface ISuiteSpec { - name: string; - completionSpecs: Fig.Spec | Fig.Spec[]; - // TODO: This seems unnecessary, ideally getCompletionItemsFromSpecs would only consider the - // spec's completions - availableCommands: string | string[]; - testSpecs: ITestSpec2[]; -} - -interface ITestSpec2 { - input: string; - expectedResourceRequests?: { - type: 'files' | 'folders' | 'both'; - cwd: Uri; - }; - expectedCompletions?: string[]; -} - -function createCodeTestSpecs(executable: string): ITestSpec2[] { - const codeOptions = ['-', '--add', '--category', '--diff', '--disable-extension', '--disable-extensions', '--disable-gpu', '--enable-proposed-api', '--extensions-dir', '--goto', '--help', '--inspect-brk-extensions', '--inspect-extensions', '--install-extension', '--list-extensions', '--locale', '--log', '--max-memory', '--merge', '--new-window', '--pre-release', '--prof-startup', '--profile', '--reuse-window', '--show-versions', '--status', '--sync', '--telemetry', '--uninstall-extension', '--user-data-dir', '--verbose', '--version', '--wait', '-a', '-d', '-g', '-h', '-m', '-n', '-r', '-s', '-v', '-w']; - const localeOptions = ['bg', 'de', 'en', 'es', 'fr', 'hu', 'it', 'ja', 'ko', 'pt-br', 'ru', 'tr', 'zh-CN', 'zh-TW']; - const categoryOptions = ['azure', 'data science', 'debuggers', 'extension packs', 'education', 'formatters', 'keymaps', 'language packs', 'linters', 'machine learning', 'notebooks', 'programming languages', 'scm providers', 'snippets', 'testing', 'themes', 'visualization', 'other']; - const logOptions = ['critical', 'error', 'warn', 'info', 'debug', 'trace', 'off']; - const syncOptions = ['on', 'off']; - - const typingTests: ITestSpec2[] = []; - for (let i = 1; i < executable.length; i++) { - const input = `${executable.slice(0, i)}|`; - typingTests.push({ input, expectedCompletions: [executable], expectedResourceRequests: input.endsWith(' ') ? undefined : { type: 'both', cwd: testCwd } }); - } - - return [ - // Typing the command - ...typingTests, - - // Basic arguments - { input: `${executable} |`, expectedCompletions: codeOptions }, - { input: `${executable} --locale |`, expectedCompletions: localeOptions }, - { input: `${executable} --diff |`, expectedResourceRequests: { type: 'files', cwd: testCwd } }, - { input: `${executable} --diff ./file1 |`, expectedResourceRequests: { type: 'files', cwd: testCwd } }, - { input: `${executable} --merge |`, expectedResourceRequests: { type: 'files', cwd: testCwd } }, - { input: `${executable} --merge ./file1 ./file2 |`, expectedResourceRequests: { type: 'files', cwd: testCwd } }, - { input: `${executable} --merge ./file1 ./file2 ./base |`, expectedResourceRequests: { type: 'files', cwd: testCwd } }, - { input: `${executable} --goto |`, expectedResourceRequests: { type: 'files', cwd: testCwd } }, - { input: `${executable} --user-data-dir |`, expectedResourceRequests: { type: 'folders', cwd: testCwd } }, - { input: `${executable} --profile |`, expectedResourceRequests: { type: 'both', cwd: testCwd } }, - { input: `${executable} --install-extension |`, expectedResourceRequests: { type: 'both', cwd: testCwd } }, - { input: `${executable} --uninstall-extension |`, expectedResourceRequests: { type: 'both', cwd: testCwd } }, - { input: `${executable} --log |`, expectedCompletions: logOptions }, - { input: `${executable} --sync |`, expectedCompletions: syncOptions }, - { input: `${executable} --extensions-dir |`, expectedResourceRequests: { type: 'folders', cwd: testCwd } }, - { input: `${executable} --list-extensions |`, expectedCompletions: codeOptions }, - { input: `${executable} --show-versions |`, expectedCompletions: codeOptions }, - { input: `${executable} --category |`, expectedCompletions: categoryOptions }, - { input: `${executable} --category a|`, expectedCompletions: categoryOptions.filter(c => c.startsWith('a')) }, - - // Middle of command - { input: `${executable} | --locale`, expectedCompletions: codeOptions }, - ]; -} +import { cdTestSuiteSpec as cdTestSuite } from './completions/cd.test'; +import { codeTestSuite } from './completions/code.test'; +import { testPaths, type ISuiteSpec } from './helpers'; +import { codeInsidersTestSuite } from './completions/code-insiders.test'; const testSpecs2: ISuiteSpec[] = [ { @@ -86,56 +19,15 @@ const testSpecs2: ISuiteSpec[] = [ completionSpecs: [], availableCommands: [], testSpecs: [ - { input: '|', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testCwd } }, - { input: '|.', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testCwd } }, - { input: '|./', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testCwd } }, - { input: 'fakecommand |', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testCwd } }, + { input: '|', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: '|.', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: '|./', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'fakecommand |', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, ] }, - { - name: 'cd', - completionSpecs: cdSpec, - availableCommands: 'cd', - testSpecs: [ - // Typing a path - { input: '.|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testCwd } }, - { input: './|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testCwd } }, - { input: './.|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testCwd } }, - // Typing the command - { input: 'c|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testCwd } }, - { input: 'cd|', expectedCompletions: ['cd'], expectedResourceRequests: { type: 'both', cwd: testCwd } }, - - // Basic arguments - { input: 'cd |', expectedCompletions: ['~', '-'], expectedResourceRequests: { type: 'folders', cwd: testCwd } }, - { input: 'cd -|', expectedCompletions: ['-'], expectedResourceRequests: { type: 'folders', cwd: testCwd } }, - { input: 'cd ~|', expectedCompletions: ['~'], expectedResourceRequests: { type: 'folders', cwd: testCwd } }, - - // Relative paths - { input: 'cd c|', expectedResourceRequests: { type: 'folders', cwd: testCwd } }, - { input: 'cd child|', expectedResourceRequests: { type: 'folders', cwd: testCwd } }, - { input: 'cd .|', expectedResourceRequests: { type: 'folders', cwd: testCwd } }, - { input: 'cd ./|', expectedResourceRequests: { type: 'folders', cwd: testCwd } }, - { input: 'cd ./child|', expectedResourceRequests: { type: 'folders', cwd: testCwd } }, - { input: 'cd ..|', expectedResourceRequests: { type: 'folders', cwd: testCwd } }, - - // Relative directories (changes cwd due to /) - { input: 'cd child/|', expectedResourceRequests: { type: 'folders', cwd: testCwdChild } }, - { input: 'cd ../|', expectedResourceRequests: { type: 'folders', cwd: testCwdParent } }, - { input: 'cd ../sibling|', expectedResourceRequests: { type: 'folders', cwd: testCwdParent } }, - ] - }, - { - name: 'code', - completionSpecs: codeCompletionSpec, - availableCommands: 'code', - testSpecs: createCodeTestSpecs('code') - }, - { - name: 'code-insiders', - completionSpecs: codeInsidersCompletionSpec, - availableCommands: 'code-insiders', - testSpecs: createCodeTestSpecs('code-insiders') - } + cdTestSuite, + codeTestSuite, + codeInsidersTestSuite, ]; suite('Terminal Suggest', () => { @@ -147,7 +39,7 @@ suite('Terminal Suggest', () => { let expectedString = testSpec.expectedCompletions ? `[${testSpec.expectedCompletions.map(e => `'${e}'`).join(', ')}]` : '[]'; if (testSpec.expectedResourceRequests) { expectedString += ` + ${testSpec.expectedResourceRequests.type}`; - if (testSpec.expectedResourceRequests.cwd.fsPath !== testCwd.fsPath) { + if (testSpec.expectedResourceRequests.cwd.fsPath !== testPaths.cwd.fsPath) { expectedString += ` @ ${basename(testSpec.expectedResourceRequests.cwd.fsPath)}/`; } } @@ -158,7 +50,14 @@ suite('Terminal Suggest', () => { const filesRequested = testSpec.expectedResourceRequests?.type === 'files' || testSpec.expectedResourceRequests?.type === 'both'; const foldersRequested = testSpec.expectedResourceRequests?.type === 'folders' || testSpec.expectedResourceRequests?.type === 'both'; const terminalContext = { commandLine, cursorPosition }; - const result = await getCompletionItemsFromSpecs(completionSpecs, terminalContext, availableCommands.map(c => { return { label: c }; }), prefix, getTokenType(terminalContext, undefined), testCwd); + const result = await getCompletionItemsFromSpecs( + completionSpecs, + terminalContext, + availableCommands.map(c => { return { label: c }; }), + prefix, + getTokenType(terminalContext, undefined), + testPaths.cwd + ); deepStrictEqual(result.items.map(i => i.label).sort(), (testSpec.expectedCompletions ?? []).sort()); strictEqual(result.filesRequested, filesRequested); strictEqual(result.foldersRequested, foldersRequested); From 6d43829c8d0698d92ed383065ddf47f65256f571 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Feb 2025 06:39:02 -0800 Subject: [PATCH 0350/2632] ls spec unit tests --- .../src/test/completions/upstream/ls.test.ts | 101 ++++++++++++++++++ .../src/test/terminalSuggestMain.test.ts | 6 ++ 2 files changed, 107 insertions(+) create mode 100644 extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts diff --git a/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts new file mode 100644 index 000000000000..33bb80d88220 --- /dev/null +++ b/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import { testPaths, type ISuiteSpec } from '../../helpers'; +import lsSpec from '../../../completions/upstream/ls'; + +const allOptions = [ + '-%', + '-,', + '--color', + '-1', + '-@', + '-A', + '-B', + '-C', + '-F', + '-G', + '-H', + '-L', + '-O', + '-P', + '-R', + '-S', + '-T', + '-U', + '-W', + '-a', + '-b', + '-c', + '-d', + '-e', + '-f', + '-g', + '-h', + '-i', + '-k', + '-l', + '-m', + '-n', + '-o', + '-p', + '-q', + '-r', + '-s', + '-t', + '-u', + '-v', + '-w', + '-x', +]; + +export function removeEntry(array: T[], element: T): T[] { + const index = array.indexOf(element); + if (index > -1) { + array.splice(index, 1); + } + return array; +} + +export const lsTestSuiteSpec: ISuiteSpec = { + name: 'ls', + completionSpecs: lsSpec, + availableCommands: 'ls', + testSpecs: [ + // Empty input + { input: '|', expectedCompletions: ['ls'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Typing the command + { input: 'l|', expectedCompletions: ['ls'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ls|', expectedCompletions: ['ls'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Basic options + // TODO: The spec wants file paths and folders (which seems like it should only be folders), + // but neither are requested + { input: 'ls |', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + { input: 'ls -|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + + // Filtering options should request all options so client side can filter + { input: 'ls -a|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + + // Duplicate option + // TODO: Duplicate options should not be presented + // { input: 'ls -a -|', expectedCompletions: removeEntry(allOptions, '-a'), expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + + // Relative paths + { input: 'ls c|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + { input: 'ls child|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + { input: 'ls .|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + { input: 'ls ./|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + { input: 'ls ./child|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + { input: 'ls ..|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + + // Relative directories (changes cwd due to /) + { input: 'ls child/|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwdChild } + { input: 'ls ../|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwdParent } + { input: 'ls ../sibling|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwdParent } + ] +}; diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index c84693144eee..a704196df1de 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -12,6 +12,7 @@ import { cdTestSuiteSpec as cdTestSuite } from './completions/cd.test'; import { codeTestSuite } from './completions/code.test'; import { testPaths, type ISuiteSpec } from './helpers'; import { codeInsidersTestSuite } from './completions/code-insiders.test'; +import { lsTestSuiteSpec } from './completions/upstream/ls.test'; const testSpecs2: ISuiteSpec[] = [ { @@ -25,9 +26,14 @@ const testSpecs2: ISuiteSpec[] = [ { input: 'fakecommand |', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, ] }, + + // completions/ cdTestSuite, codeTestSuite, codeInsidersTestSuite, + + // completions/upstream/ + lsTestSuiteSpec, ]; suite('Terminal Suggest', () => { From 4e364a5c7b01a173af2f7b8c094840080bed4ace Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Feb 2025 06:50:57 -0800 Subject: [PATCH 0351/2632] Add issue references --- .../src/test/completions/upstream/ls.test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts index 33bb80d88220..e8f0b7de7b5b 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts @@ -52,13 +52,13 @@ const allOptions = [ '-x', ]; -export function removeEntry(array: T[], element: T): T[] { - const index = array.indexOf(element); - if (index > -1) { - array.splice(index, 1); - } - return array; -} +// function removeEntry(array: T[], element: T): T[] { +// const index = array.indexOf(element); +// if (index > -1) { +// array.splice(index, 1); +// } +// return array; +// } export const lsTestSuiteSpec: ISuiteSpec = { name: 'ls', @@ -74,7 +74,7 @@ export const lsTestSuiteSpec: ISuiteSpec = { // Basic options // TODO: The spec wants file paths and folders (which seems like it should only be folders), - // but neither are requested + // but neither are requested https://github.com/microsoft/vscode/issues/239606 { input: 'ls |', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } { input: 'ls -|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } @@ -82,7 +82,7 @@ export const lsTestSuiteSpec: ISuiteSpec = { { input: 'ls -a|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } // Duplicate option - // TODO: Duplicate options should not be presented + // TODO: Duplicate options should not be presented https://github.com/microsoft/vscode/issues/239607 // { input: 'ls -a -|', expectedCompletions: removeEntry(allOptions, '-a'), expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } // Relative paths From 79c194bfda67d2906e0059dde79ce565f4fb09d9 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 4 Feb 2025 15:56:22 +0100 Subject: [PATCH 0352/2632] fix https://github.com/microsoft/vscode-copilot/issues/12823 (#239603) --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index e61430a76851..84b7734190ad 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -532,6 +532,9 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { const context: IChatContentPartRenderContext = { element, @@ -543,6 +546,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer this._onDidClickRerunWithAgentOrCommandDetection.fire({ sessionId: element.sessionId, requestId: element.id })); templateData.value.appendChild(cmdPart.domNode); parts.push(cmdPart); + inlineSlashCommandRendered = true; } templateData.value.appendChild(newPart.domNode); From ad0173f59aacf537b06b861b0ee87421bae92619 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Feb 2025 07:00:38 -0800 Subject: [PATCH 0353/2632] echo fig spec tests --- .../test/completions/upstream/echo.test.ts | 38 +++++++++++++++++++ .../src/test/completions/upstream/ls.test.ts | 10 +---- .../terminal-suggest/src/test/helpers.ts | 10 +++++ .../src/test/terminalSuggestMain.test.ts | 2 + 4 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts diff --git a/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts new file mode 100644 index 000000000000..7334c8d219b0 --- /dev/null +++ b/extensions/terminal-suggest/src/test/completions/upstream/echo.test.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import { testPaths, type ISuiteSpec } from '../../helpers'; +import echoSpec from '../../../completions/upstream/echo'; + +const allOptions = [ + '-E', + '-e', + '-n', +]; + +export const echoTestSuiteSpec: ISuiteSpec = { + name: 'echo', + completionSpecs: echoSpec, + availableCommands: 'echo', + testSpecs: [ + // Empty input + { input: '|', expectedCompletions: ['echo'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Typing the command + { input: 'e|', expectedCompletions: ['echo'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ec|', expectedCompletions: ['echo'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ech|', expectedCompletions: ['echo'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'echo|', expectedCompletions: ['echo'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Basic options + { input: 'echo |', expectedCompletions: allOptions }, + + // Duplicate option + // TODO: Duplicate options should not be presented https://github.com/microsoft/vscode/issues/239607 + // { input: 'echo -e -|', expectedCompletions: removeArrayEntries(allOptions, '-e') }, + // { input: 'echo -e -E -|', expectedCompletions: removeArrayEntries(allOptions, '-e', '-E') }, + ] +}; diff --git a/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts index e8f0b7de7b5b..dfd7da8a93e9 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts @@ -52,14 +52,6 @@ const allOptions = [ '-x', ]; -// function removeEntry(array: T[], element: T): T[] { -// const index = array.indexOf(element); -// if (index > -1) { -// array.splice(index, 1); -// } -// return array; -// } - export const lsTestSuiteSpec: ISuiteSpec = { name: 'ls', completionSpecs: lsSpec, @@ -83,7 +75,7 @@ export const lsTestSuiteSpec: ISuiteSpec = { // Duplicate option // TODO: Duplicate options should not be presented https://github.com/microsoft/vscode/issues/239607 - // { input: 'ls -a -|', expectedCompletions: removeEntry(allOptions, '-a'), expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + // { input: 'ls -a -|', expectedCompletions: removeArrayEntry(allOptions, '-a'), expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } // Relative paths { input: 'ls c|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } diff --git a/extensions/terminal-suggest/src/test/helpers.ts b/extensions/terminal-suggest/src/test/helpers.ts index 3075022d541f..48ad02c06787 100644 --- a/extensions/terminal-suggest/src/test/helpers.ts +++ b/extensions/terminal-suggest/src/test/helpers.ts @@ -36,3 +36,13 @@ export const testPaths = { cwd: vscode.Uri.joinPath(fixtureDir, 'parent/home'), cwdChild: vscode.Uri.joinPath(fixtureDir, 'parent/home/child'), }; + +export function removeArrayEntries(array: T[], ...elements: T[]): T[] { + for (const element of elements) { + const index = array.indexOf(element); + if (index > -1) { + array.splice(index, 1); + } + } + return array; +} diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index a704196df1de..f097db24d8ab 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -13,6 +13,7 @@ import { codeTestSuite } from './completions/code.test'; import { testPaths, type ISuiteSpec } from './helpers'; import { codeInsidersTestSuite } from './completions/code-insiders.test'; import { lsTestSuiteSpec } from './completions/upstream/ls.test'; +import { echoTestSuiteSpec } from './completions/upstream/echo.test'; const testSpecs2: ISuiteSpec[] = [ { @@ -33,6 +34,7 @@ const testSpecs2: ISuiteSpec[] = [ codeInsidersTestSuite, // completions/upstream/ + echoTestSuiteSpec, lsTestSuiteSpec, ]; From 77f6745f4279eb2bd24693cb6ecc1b824785b349 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Feb 2025 07:04:49 -0800 Subject: [PATCH 0354/2632] mkdir fig spec tests --- .../test/completions/upstream/mkdir.test.ts | 43 +++++++++++++++++++ .../src/test/terminalSuggestMain.test.ts | 2 + 2 files changed, 45 insertions(+) create mode 100644 extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts diff --git a/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts new file mode 100644 index 000000000000..f471a6650461 --- /dev/null +++ b/extensions/terminal-suggest/src/test/completions/upstream/mkdir.test.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import { testPaths, type ISuiteSpec } from '../../helpers'; +import mkdirSpec from '../../../completions/upstream/mkdir'; + +const allOptions = [ + '--context', + '--help', + '--mode', + '--parents', + '--verbose', + '--version', + '-Z', + '-m', + '-p', + '-v', +]; + +export const mkdirTestSuiteSpec: ISuiteSpec = { + name: 'mkdir', + completionSpecs: mkdirSpec, + availableCommands: 'mkdir', + testSpecs: [ + // Empty input + { input: '|', expectedCompletions: ['mkdir'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Typing the command + { input: 'm|', expectedCompletions: ['mkdir'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'mkdir|', expectedCompletions: ['mkdir'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Basic options + { input: 'mkdir |', expectedCompletions: allOptions, expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + + // Duplicate option + // TODO: Duplicate options should not be presented https://github.com/microsoft/vscode/issues/239607 + // { input: 'mkdir -Z -|', expectedCompletions: removeArrayEntries(allOptions, '-z') }, + // { input: 'mkdir -Z -m -|', expectedCompletions: removeArrayEntries(allOptions, '-z', '-m') }, + ] +}; diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index f097db24d8ab..a79f1c7432af 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -14,6 +14,7 @@ import { testPaths, type ISuiteSpec } from './helpers'; import { codeInsidersTestSuite } from './completions/code-insiders.test'; import { lsTestSuiteSpec } from './completions/upstream/ls.test'; import { echoTestSuiteSpec } from './completions/upstream/echo.test'; +import { mkdirTestSuiteSpec } from './completions/upstream/mkdir.test'; const testSpecs2: ISuiteSpec[] = [ { @@ -36,6 +37,7 @@ const testSpecs2: ISuiteSpec[] = [ // completions/upstream/ echoTestSuiteSpec, lsTestSuiteSpec, + mkdirTestSuiteSpec, ]; suite('Terminal Suggest', () => { From c54d20f63c596826d9e3c96fcc1e2ab09b49c341 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:08:34 +0100 Subject: [PATCH 0355/2632] Fix scrolling issue when pressing Enter on multi-line suggestions (#239605) fixes https://github.com/microsoft/vscode/issues/239599 --- .../browser/view/ghostText/ghostTextView.ts | 11 +++++++++-- .../browser/view/inlineCompletionsView.ts | 16 ++++++++++------ .../browser/view/inlineEdits/insertionView.ts | 1 + 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts index 47644496fea2..a7ed17ddad61 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts @@ -43,6 +43,7 @@ export class GhostTextView extends Disposable { extraClasses?: string[]; syntaxHighlightingEnabled: boolean; }>, + private readonly _shouldKeepCursorStable: boolean, @ILanguageService private readonly _languageService: ILanguageService, ) { super(); @@ -151,7 +152,8 @@ export class GhostTextView extends Disposable { minReservedLineCount: uiState.additionalReservedLineCount, targetTextModel: uiState.targetTextModel, } : undefined; - }) + }), + this._shouldKeepCursorStable ) ); @@ -257,7 +259,8 @@ export class AdditionalLinesWidget extends Disposable { lineNumber: number; additionalLines: LineData[]; minReservedLineCount: number; - } | undefined> + } | undefined>, + private readonly shouldKeepCursorStable: boolean ) { super(); @@ -334,6 +337,10 @@ export class AdditionalLinesWidget extends Disposable { } private keepCursorStable(lineNumber: number, heightInLines: number): void { + if (!this.shouldKeepCursorStable) { + return; + } + const cursorLineNumber = this.editor.getSelection()?.getStartPosition()?.lineNumber; if (cursorLineNumber !== undefined && lineNumber < cursorLineNumber) { this.editor.setScrollTop(this.editor.getScrollTop() + heightInLines * this.editor.getOption(EditorOption.lineHeight)); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts index cbbbb35734e2..7cd21d3b088e 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineCompletionsView.ts @@ -25,12 +25,16 @@ export class InlineCompletionsView extends Disposable { private readonly _stablizedGhostTexts = convertItemsToStableObservables(this._ghostTexts, this._store); private readonly _editorObs = observableCodeEditor(this._editor); - private readonly _ghostTextWidgets = mapObservableArrayCached(this, this._stablizedGhostTexts, (ghostText, store) => derivedDisposable((reader) => this._instantiationService.createInstance(readHotReloadableExport(GhostTextView, reader), this._editor, { - ghostText: ghostText, - minReservedLineCount: constObservable(0), - targetTextModel: this._model.map(v => v?.textModel), - }, - this._editorObs.getOption(EditorOption.inlineSuggest).map(v => ({ syntaxHighlightingEnabled: v.syntaxHighlightingEnabled }))) + private readonly _ghostTextWidgets = mapObservableArrayCached(this, this._stablizedGhostTexts, (ghostText, store) => derivedDisposable((reader) => this._instantiationService.createInstance(readHotReloadableExport(GhostTextView, reader), + this._editor, + { + ghostText: ghostText, + minReservedLineCount: constObservable(0), + targetTextModel: this._model.map(v => v?.textModel), + }, + this._editorObs.getOption(EditorOption.inlineSuggest).map(v => ({ syntaxHighlightingEnabled: v.syntaxHighlightingEnabled })), + false, + ) ).recomputeInitiallyAndOnChange(store) ).recomputeInitiallyAndOnChange(this._store); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/insertionView.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/insertionView.ts index 0f545a59f56a..47ee5171571c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/insertionView.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/insertionView.ts @@ -54,6 +54,7 @@ export class InlineEditsInsertionView extends Disposable implements IInlineEdits targetTextModel: this._editorObs.model.map(model => model ?? undefined), }, observableValue(this, { syntaxHighlightingEnabled: true, extraClasses: ['inline-edit'] }), + true, )); constructor( From f614d9d870e308477a5d452b0e3abaa28bec4b21 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 4 Feb 2025 16:30:29 +0100 Subject: [PATCH 0356/2632] leak - fix leaking file widgets (#239612) --- .../contrib/chat/browser/chatMarkdownDecorationsRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts index 922e47f563a6..46a068ee3dce 100644 --- a/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatMarkdownDecorationsRenderer.ts @@ -250,7 +250,7 @@ export class ChatMarkdownDecorationsRenderer { } const inlineAnchor = store.add(this.instantiationService.createInstance(InlineAnchorWidget, a, data)); - this.chatMarkdownAnchorService.register(inlineAnchor); + store.add(this.chatMarkdownAnchorService.register(inlineAnchor)); } private renderResourceWidget(name: string, args: IDecorationWidgetArgs | undefined, store: DisposableStore): HTMLElement { From a2a0272687a193cc50dd3eecc9cc03e0a8b8821a Mon Sep 17 00:00:00 2001 From: Vitaly Date: Tue, 4 Feb 2025 18:35:47 +0300 Subject: [PATCH 0357/2632] [Git] Migrate to git autostash when pulling for better performance (#187850) * git: migrate to git autostash when pulling (better performance) * should be implemented correctly! * refactor other op * Pull request feedback --------- Co-authored-by: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> --- extensions/git/src/git.ts | 10 ++++++++-- extensions/git/src/repository.ts | 7 +++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 431d50b88afe..b6d5582b8fb6 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1127,8 +1127,9 @@ function parseGitBlame(data: string): BlameInformation[] { } export interface PullOptions { - unshallow?: boolean; - tags?: boolean; + readonly unshallow?: boolean; + readonly tags?: boolean; + readonly autoStash?: boolean; readonly cancellationToken?: CancellationToken; } @@ -2087,6 +2088,11 @@ export class Repository { args.push('--unshallow'); } + // --auto-stash option is only available `git pull --merge` starting with git 2.27.0 + if (options.autoStash && this._git.compareGitVersionTo('2.27.0') !== -1) { + args.push('--autostash'); + } + if (rebase) { args.push('-r'); } diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index e791e3d500d5..560477c586d5 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1798,6 +1798,7 @@ export class Repository implements Disposable { await this.run(Operation.Pull, async () => { await this.maybeAutoStash(async () => { const config = workspace.getConfiguration('git', Uri.file(this.root)); + const autoStash = config.get('autoStash'); const fetchOnPull = config.get('fetchOnPull'); const tags = config.get('pullTags'); @@ -1807,7 +1808,7 @@ export class Repository implements Disposable { } if (await this.checkIfMaybeRebased(this.HEAD?.name)) { - await this._pullAndHandleTagConflict(rebase, remote, branch, { unshallow, tags }); + await this._pullAndHandleTagConflict(rebase, remote, branch, { unshallow, tags, autoStash }); } }); }); @@ -1881,6 +1882,7 @@ export class Repository implements Disposable { await this.run(Operation.Sync, async () => { await this.maybeAutoStash(async () => { const config = workspace.getConfiguration('git', Uri.file(this.root)); + const autoStash = config.get('autoStash'); const fetchOnPull = config.get('fetchOnPull'); const tags = config.get('pullTags'); const followTags = config.get('followTagsWhenSync'); @@ -1893,7 +1895,7 @@ export class Repository implements Disposable { } if (await this.checkIfMaybeRebased(this.HEAD?.name)) { - await this._pullAndHandleTagConflict(rebase, remoteName, pullBranch, { tags, cancellationToken }); + await this._pullAndHandleTagConflict(rebase, remoteName, pullBranch, { tags, cancellationToken, autoStash }); } }; @@ -2530,6 +2532,7 @@ export class Repository implements Disposable { private async maybeAutoStash(runOperation: () => Promise): Promise { const config = workspace.getConfiguration('git', Uri.file(this.root)); const shouldAutoStash = config.get('autoStash') + && this.repository.git.compareGitVersionTo('2.27.0') < 0 && (this.indexGroup.resourceStates.length > 0 || this.workingTreeGroup.resourceStates.some( r => r.type !== Status.UNTRACKED && r.type !== Status.IGNORED)); From 43469df88d9b6a4bbcd49043b959389941ac2248 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 4 Feb 2025 16:36:58 +0100 Subject: [PATCH 0358/2632] chore - make `reviewEdits` a dedicated function (#239611) --- .../browser/actions/chatCodeblockActions.ts | 5 +- .../browser/actions/codeBlockOperations.ts | 10 +-- .../browser/inlineChatController.ts | 80 +++++++++---------- 3 files changed, 47 insertions(+), 48 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 247fc3082701..5448cdeed929 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -27,7 +27,7 @@ import { IWorkbenchContribution } from '../../../../common/contributions.js'; import { IUntitledTextResourceEditorInput } from '../../../../common/editor.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { accessibleViewInCodeBlock } from '../../../accessibility/browser/accessibilityConfiguration.js'; -import { InlineChatController } from '../../../inlineChat/browser/inlineChatController.js'; +import { InlineChatController, reviewEdits } from '../../../inlineChat/browser/inlineChatController.js'; import { ITerminalEditorService, ITerminalGroupService, ITerminalService } from '../../../terminal/browser/terminal.js'; import { ChatAgentLocation } from '../../common/chatAgents.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; @@ -575,6 +575,7 @@ export function registerChatCodeCompareBlockActions() { async runWithContext(accessor: ServicesAccessor, context: ICodeCompareBlockActionContext): Promise { + const instaService = accessor.get(IInstantiationService); const editorService = accessor.get(ICodeEditorService); const item = context.edit; @@ -601,7 +602,7 @@ export function registerChatCodeCompareBlockActions() { const inlineChatController = InlineChatController.get(editorToApply); if (inlineChatController) { editorToApply.revealLineInCenterIfOutsideViewport(firstEdit.range.startLineNumber); - inlineChatController.reviewEdits(textEdits, CancellationToken.None); + instaService.invokeFunction(reviewEdits, editorToApply, textEdits, CancellationToken.None); response.setEditApplied(item, 1); return true; } diff --git a/src/vs/workbench/contrib/chat/browser/actions/codeBlockOperations.ts b/src/vs/workbench/contrib/chat/browser/actions/codeBlockOperations.ts index 03de808a6428..8831b4880040 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/codeBlockOperations.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/codeBlockOperations.ts @@ -24,7 +24,7 @@ import { ILogService } from '../../../../../platform/log/common/log.js'; import { IProgressService, ProgressLocation } from '../../../../../platform/progress/common/progress.js'; import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { ITextFileService } from '../../../../services/textfile/common/textfiles.js'; -import { InlineChatController } from '../../../inlineChat/browser/inlineChatController.js'; +import { reviewEdits } from '../../../inlineChat/browser/inlineChatController.js'; import { insertCell } from '../../../notebook/browser/controller/cellOperations.js'; import { IActiveNotebookEditor, INotebookEditor } from '../../../notebook/browser/notebookBrowser.js'; import { CellKind, NOTEBOOK_EDITOR_ID } from '../../../notebook/common/notebookCommon.js'; @@ -34,6 +34,7 @@ import { isResponseVM } from '../../common/chatViewModel.js'; import { ICodeBlockActionContext } from '../codeBlockPart.js'; import { IQuickInputService } from '../../../../../platform/quickinput/common/quickInput.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; export class InsertCodeBlockOperation { constructor( @@ -115,6 +116,7 @@ export class ApplyCodeBlockOperation { @IProgressService private readonly progressService: IProgressService, @IQuickInputService private readonly quickInputService: IQuickInputService, @ILabelService private readonly labelService: ILabelService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { } @@ -303,11 +305,7 @@ export class ApplyCodeBlockOperation { } private async applyWithInlinePreview(edits: AsyncIterable, codeEditor: IActiveCodeEditor, tokenSource: CancellationTokenSource): Promise { - const inlineChatController = InlineChatController.get(codeEditor); - if (inlineChatController) { - return inlineChatController.reviewEdits(edits, tokenSource.token); - } - return false; + return this.instantiationService.invokeFunction(reviewEdits, codeEditor, edits, tokenSource.token); } private tryToRevealCodeBlock(codeEditor: IActiveCodeEditor, codeBlock: string): void { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 3ef89c1c8f11..4154f8239f90 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -141,7 +141,6 @@ export class InlineChatController implements IEditorContribution { @IDialogService private readonly _dialogService: IDialogService, @IContextKeyService contextKeyService: IContextKeyService, @IChatService private readonly _chatService: IChatService, - @IChatEditingService private readonly _chatEditingService: IChatEditingService, @IEditorService private readonly _editorService: IEditorService, @INotebookEditorService notebookEditorService: INotebookEditorService, ) { @@ -985,9 +984,6 @@ export class InlineChatController implements IEditorContribution { } } - cancelCurrentRequest(): void { - this._messages.fire(Message.CANCEL_INPUT | Message.CANCEL_REQUEST); - } arrowOut(up: boolean): void { if (this._ui.value.position && this._editor.hasModel()) { @@ -1127,57 +1123,61 @@ export class InlineChatController implements IEditorContribution { joinCurrentRun(): Promise | undefined { return this._currentRun; } +} - async reviewEdits(stream: AsyncIterable, token: CancellationToken) { - if (!this._editor.hasModel()) { - return false; - } - const uri = this._editor.getModel().uri; - const chatModel = this._chatService.startSession(ChatAgentLocation.Editor, token); +export async function reviewEdits(accessor: ServicesAccessor, editor: ICodeEditor, stream: AsyncIterable, token: CancellationToken): Promise { + if (!editor.hasModel()) { + return false; + } - const editSession = await this._chatEditingService.createEditingSession(chatModel.sessionId); + const chatService = accessor.get(IChatService); + const chatEditingService = accessor.get(IChatEditingService); - // - const store = new DisposableStore(); - store.add(chatModel); - store.add(editSession); + const uri = editor.getModel().uri; + const chatModel = chatService.startSession(ChatAgentLocation.Editor, token); - // STREAM - const chatRequest = chatModel?.addRequest({ text: '', parts: [] }, { variables: [] }, 0); - assertType(chatRequest.response); - chatRequest.response.updateContent({ kind: 'textEdit', uri, edits: [], done: false }); - for await (const chunk of stream) { + const editSession = await chatEditingService.createEditingSession(chatModel.sessionId); - if (token.isCancellationRequested) { - chatRequest.response.cancel(); - break; - } + const store = new DisposableStore(); + store.add(chatModel); + store.add(editSession); - chatRequest.response.updateContent({ kind: 'textEdit', uri, edits: chunk, done: false }); - } - chatRequest.response.updateContent({ kind: 'textEdit', uri, edits: [], done: true }); + // STREAM + const chatRequest = chatModel?.addRequest({ text: '', parts: [] }, { variables: [] }, 0); + assertType(chatRequest.response); + chatRequest.response.updateContent({ kind: 'textEdit', uri, edits: [], done: false }); + for await (const chunk of stream) { - if (!token.isCancellationRequested) { - chatRequest.response.complete(); + if (token.isCancellationRequested) { + chatRequest.response.cancel(); + break; } - const whenDecided = new Promise(resolve => { - store.add(autorun(r => { - if (!editSession.entries.read(r).some(e => e.state.read(r) === WorkingSetEntryState.Modified)) { - resolve(undefined); - } - })); - }); + chatRequest.response.updateContent({ kind: 'textEdit', uri, edits: chunk, done: false }); + } + chatRequest.response.updateContent({ kind: 'textEdit', uri, edits: [], done: true }); - await raceCancellation(whenDecided, token); + if (!token.isCancellationRequested) { + chatRequest.response.complete(); + } - store.dispose(); + const whenDecided = new Promise(resolve => { + store.add(autorun(r => { + if (!editSession.entries.read(r).some(e => e.state.read(r) === WorkingSetEntryState.Modified)) { + resolve(undefined); + } + })); + }); - return true; - } + await raceCancellation(whenDecided, token); + + store.dispose(); + + return true; } + async function moveToPanelChat(accessor: ServicesAccessor, model: ChatModel | undefined) { const viewsService = accessor.get(IViewsService); From d04d44610f9dc5dc1c9eadb1e177fcb1c17693a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 08:05:29 -0800 Subject: [PATCH 0359/2632] Bump openssl from 0.10.66 to 0.10.70 in /cli (#239517) Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.66 to 0.10.70. - [Release notes](https://github.com/sfackler/rust-openssl/releases) - [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.66...openssl-v0.10.70) --- updated-dependencies: - dependency-name: openssl dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- cli/Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 5da9906e1acd..ff45765a0c1d 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -1717,9 +1717,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" dependencies = [ "bitflags 2.5.0", "cfg-if", @@ -1749,9 +1749,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" dependencies = [ "cc", "libc", From 776bf43309b3a6d5ebbffcf302b092fa16e678d1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 4 Feb 2025 17:34:53 +0100 Subject: [PATCH 0360/2632] chore - remove dead code and align controllers a little (#239619) --- .../browser/actions/chatQuickInputActions.ts | 33 ------------------- .../browser/inlineChatController.ts | 25 +++----------- .../browser/inlineChatController2.ts | 4 +++ .../contrib/inlineChat/common/inlineChat.ts | 3 -- .../electron-sandbox/inlineChatActions.ts | 2 +- .../test/browser/inlineChatController.test.ts | 10 +++--- 6 files changed, 13 insertions(+), 64 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index 3c75e0c503c0..2266ef267ee5 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -5,7 +5,6 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; -import { ICodeEditorService } from '../../../../../editor/browser/services/codeEditorService.js'; import { Selection } from '../../../../../editor/common/core/selection.js'; import { localize, localize2 } from '../../../../../nls.js'; import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js'; @@ -14,7 +13,6 @@ import { KeybindingWeight } from '../../../../../platform/keybinding/common/keyb import { CHAT_CATEGORY } from './chatActions.js'; import { IQuickChatOpenOptions, IQuickChatService } from '../chat.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { InlineChatController } from '../../../inlineChat/browser/inlineChatController.js'; export const ASK_QUICK_QUESTION_ACTION_ID = 'workbench.action.quickchat.toggle'; export function registerQuickChatActions() { @@ -65,37 +63,6 @@ export function registerQuickChatActions() { } }); - registerAction2(class LaunchInlineChatFromQuickChatAction extends Action2 { - constructor() { - super({ - id: 'workbench.action.quickchat.launchInlineChat', - title: localize2('chat.launchInlineChat.label', "Launch Inline Chat"), - f1: false, - category: CHAT_CATEGORY - }); - } - - async run(accessor: ServicesAccessor) { - const quickChatService = accessor.get(IQuickChatService); - const codeEditorService = accessor.get(ICodeEditorService); - if (quickChatService.focused) { - quickChatService.close(); - } - const codeEditor = codeEditorService.getActiveCodeEditor(); - if (!codeEditor) { - return; - } - - const controller = InlineChatController.get(codeEditor); - if (!controller) { - return; - } - - await controller.run(); - controller.focus(); - } - }); - } class QuickChatGlobalAction extends Action2 { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 4154f8239f90..f3c7605e6e52 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -45,7 +45,7 @@ import { IChatEditingService, WorkingSetEntryState } from '../../chat/common/cha import { ChatModel, ChatRequestRemovalReason, IChatRequestModel, IChatTextEditGroup, IChatTextEditGroupState, IResponse } from '../../chat/common/chatModel.js'; import { IChatService } from '../../chat/common/chatService.js'; import { INotebookEditorService } from '../../notebook/browser/services/notebookEditorService.js'; -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, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from '../common/inlineChat.js'; +import { CTX_INLINE_CHAT_EDITING, CTX_INLINE_CHAT_REQUEST_IN_PROGRESS, CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_VISIBLE, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseType } from '../common/inlineChat.js'; import { HunkInformation, Session, StashedSession } from './inlineChatSession.js'; import { IInlineChatSessionService } from './inlineChatSessionService.js'; import { InlineChatError } from './inlineChatSessionServiceImpl.js'; @@ -110,7 +110,6 @@ export class InlineChatController implements IEditorContribution { private readonly _ctxVisible: IContextKey; private readonly _ctxEditing: IContextKey; private readonly _ctxResponseType: IContextKey; - private readonly _ctxUserDidEdit: IContextKey; private readonly _ctxRequestInProgress: IContextKey; private readonly _ctxResponse: IContextKey; @@ -146,7 +145,6 @@ export class InlineChatController implements IEditorContribution { ) { this._ctxVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService); this._ctxEditing = CTX_INLINE_CHAT_EDITING.bindTo(contextKeyService); - this._ctxUserDidEdit = CTX_INLINE_CHAT_USER_DID_EDIT.bindTo(contextKeyService); this._ctxResponseType = CTX_INLINE_CHAT_RESPONSE_TYPE.bindTo(contextKeyService); this._ctxRequestInProgress = CTX_INLINE_CHAT_REQUEST_IN_PROGRESS.bindTo(contextKeyService); @@ -409,13 +407,9 @@ export class InlineChatController implements IEditorContribution { this._messages.fire(msg); })); - const altVersionNow = this._editor.getModel()?.getAlternativeVersionId(); this._sessionStore.add(this._editor.onDidChangeModelContent(e => { - if (!this._session?.hunkData.ignoreTextModelNChanges) { - this._ctxUserDidEdit.set(altVersionNow !== this._editor.getModel()?.getAlternativeVersionId()); - } if (this._session?.hunkData.ignoreTextModelNChanges || this._ui.value.widget.hasFocus()) { return; @@ -491,7 +485,7 @@ export class InlineChatController implements IEditorContribution { this._updatePlaceholder(); if (options.message) { - this.updateInput(options.message); + this._updateInput(options.message); aria.alert(options.message); delete options.message; this._showWidget(this._session.headless, false); @@ -888,7 +882,6 @@ export class InlineChatController implements IEditorContribution { this._sessionStore.clear(); this._ctxVisible.reset(); - this._ctxUserDidEdit.reset(); this._ui.rawValue?.hide(); @@ -969,13 +962,7 @@ export class InlineChatController implements IEditorContribution { this._ui.value.widget.placeholder = this._session?.agent.description ?? ''; } - // ---- controller API - - acceptInput() { - return this.chatWidget.acceptInput(); - } - - updateInput(text: string, selectAll = true): void { + private _updateInput(text: string, selectAll = true): void { this._ui.value.widget.chatWidget.setInput(text); if (selectAll) { @@ -984,6 +971,7 @@ export class InlineChatController implements IEditorContribution { } } + // ---- controller API arrowOut(up: boolean): void { if (this._ui.value.position && this._editor.hasModel()) { @@ -999,11 +987,6 @@ export class InlineChatController implements IEditorContribution { this._ui.value.widget.focus(); } - hasFocus(): boolean { - return this._ui.value.widget.hasFocus(); - } - - async viewInChat() { if (!this._strategy || !this._session) { return; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts index f07c82bc1548..78b47cf777de 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts @@ -232,6 +232,10 @@ export class InlineChatController2 implements IEditorContribution { markActiveController() { this._isActiveController.set(true, undefined); } + + focus() { + this._zone.rawValue?.widget.focus(); + } } export class StartSessionAction2 extends EditorAction2 { diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 21dcf39b8d66..7215c80e416f 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -82,11 +82,8 @@ export const CTX_INLINE_CHAT_RESPONSE_FOCUSED = new RawContextKey('inli export const CTX_INLINE_CHAT_EMPTY = new RawContextKey('inlineChatEmpty', false, localize('inlineChatEmpty', "Whether the interactive editor input is empty")); export const CTX_INLINE_CHAT_INNER_CURSOR_FIRST = new RawContextKey('inlineChatInnerCursorFirst', false, localize('inlineChatInnerCursorFirst', "Whether the cursor of the iteractive editor input is on the first line")); export const CTX_INLINE_CHAT_INNER_CURSOR_LAST = new RawContextKey('inlineChatInnerCursorLast', false, localize('inlineChatInnerCursorLast', "Whether the cursor of the iteractive editor input is on the last line")); -export const CTX_INLINE_CHAT_INNER_CURSOR_START = new RawContextKey('inlineChatInnerCursorStart', false, localize('inlineChatInnerCursorStart', "Whether the cursor of the iteractive editor input is on the start of the input")); -export const CTX_INLINE_CHAT_INNER_CURSOR_END = new RawContextKey('inlineChatInnerCursorEnd', false, localize('inlineChatInnerCursorEnd', "Whether the cursor of the iteractive editor input is on the end of the input")); export const CTX_INLINE_CHAT_OUTER_CURSOR_POSITION = new RawContextKey<'above' | 'below' | ''>('inlineChatOuterCursorPosition', '', localize('inlineChatOuterCursorPosition', "Whether the cursor of the outer editor is above or below the interactive editor input")); export const CTX_INLINE_CHAT_HAS_STASHED_SESSION = new RawContextKey('inlineChatHasStashedSession', false, localize('inlineChatHasStashedSession', "Whether interactive editor has kept a session for quick restore")); -export const CTX_INLINE_CHAT_USER_DID_EDIT = new RawContextKey('inlineChatUserDidEdit', undefined, localize('inlineChatUserDidEdit', "Whether the user did changes ontop of the inline chat")); export const CTX_INLINE_CHAT_CHANGE_HAS_DIFF = new RawContextKey('inlineChatChangeHasDiff', false, localize('inlineChatChangeHasDiff', "Whether the current change supports showing a diff")); export const CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF = new RawContextKey('inlineChatChangeShowsDiff', false, localize('inlineChatChangeShowsDiff', "Whether the current change showing a diff")); export const CTX_INLINE_CHAT_REQUEST_IN_PROGRESS = new RawContextKey('inlineChatRequestInProgress', false, localize('inlineChatRequestInProgress', "Whether an inline chat request is currently in progress")); diff --git a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts index ddf6acf9f638..cc0bb05c1dc2 100644 --- a/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts @@ -67,7 +67,7 @@ function holdForSpeech(accessor: ServicesAccessor, ctrl: InlineChatController, a holdMode.finally(() => { if (listening) { commandService.executeCommand(StopListeningAction.ID).finally(() => { - ctrl.acceptInput(); + ctrl.chatWidget.acceptInput(); }); } handle.dispose(); 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 1e12c2c3544c..44fe68ae3509 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -34,7 +34,7 @@ import { IChatAccessibilityService, IChatWidget, IChatWidgetService } from '../. import { ChatAgentLocation, ChatAgentService, IChatAgentData, IChatAgentNameService, IChatAgentService } from '../../../chat/common/chatAgents.js'; import { IChatResponseViewModel } from '../../../chat/common/chatViewModel.js'; import { InlineChatController, State } from '../../browser/inlineChatController.js'; -import { CTX_INLINE_CHAT_RESPONSE_TYPE, CTX_INLINE_CHAT_USER_DID_EDIT, InlineChatConfigKeys, InlineChatResponseType } from '../../common/inlineChat.js'; +import { CTX_INLINE_CHAT_RESPONSE_TYPE, InlineChatConfigKeys, InlineChatResponseType } from '../../common/inlineChat.js'; import { TestViewsService, workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js'; import { IExtensionService, nullExtensionDescription } from '../../../../services/extensions/common/extensions.js'; import { IChatProgress, IChatService } from '../../../chat/common/chatService.js'; @@ -331,7 +331,7 @@ suite('InlineChatController', function () { assert.deepStrictEqual(session.wholeRange.value, new Range(3, 1, 3, 3)); // initial ctrl.chatWidget.setInput('GENGEN'); - ctrl.acceptInput(); + ctrl.chatWidget.acceptInput(); assert.strictEqual(await ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]), undefined); assert.deepStrictEqual(session.wholeRange.value, new Range(1, 1, 4, 3)); @@ -452,7 +452,6 @@ suite('InlineChatController', function () { assert.strictEqual(await p, undefined); assert.ok(model.getValue().includes('GENERATED')); - assert.strictEqual(contextKeyService.getContextKeyValue(CTX_INLINE_CHAT_USER_DID_EDIT.key), undefined); ctrl.cancelSession(); await r; assert.ok(!model.getValue().includes('GENERATED')); @@ -470,7 +469,6 @@ suite('InlineChatController', function () { assert.ok(model.getValue().includes('GENERATED')); editor.executeEdits('test', [EditOperation.insert(model.getFullModelRange().getEndPosition(), 'MANUAL')]); - assert.strictEqual(contextKeyService.getContextKeyValue(CTX_INLINE_CHAT_USER_DID_EDIT.key), true); ctrl.finishExistingSession(); await r; @@ -548,7 +546,7 @@ suite('InlineChatController', function () { // REQUEST 2 const p2 = ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); ctrl.chatWidget.setInput('1'); - await ctrl.acceptInput(); + await ctrl.chatWidget.acceptInput(); assert.strictEqual(await p2, undefined); assert.strictEqual(model.getValue(), 'zwei-eins-'); @@ -632,7 +630,7 @@ suite('InlineChatController', function () { // REQUEST 2 const p2 = ctrl.awaitStates([State.SHOW_REQUEST, State.WAIT_FOR_INPUT]); ctrl.chatWidget.setInput('1'); - await ctrl.acceptInput(); + await ctrl.chatWidget.acceptInput(); assert.strictEqual(await p2, undefined); assert.strictEqual(model.getValue(), 'zwei\neins\nHello\nWorld\nHello Again\nHello World\n'); From 9624242dd541b620b8eccb9a3e2f44973ce63b8d Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 4 Feb 2025 17:39:12 +0100 Subject: [PATCH 0361/2632] chore - renames and moves (#239616) * chore - renames and moves * update CSS paths too --- .../browser/editorAccessibilityHelp.ts | 2 +- .../chat/browser/actions/chatClearActions.ts | 2 +- .../contrib/chat/browser/chat.contribution.ts | 6 +- .../chatEditingEditorActions.ts} | 36 +++++----- .../chatEditingEditorController.ts} | 70 +++++++++---------- .../chatEditingEditorOverlay.ts} | 52 +++++++------- .../browser/inlineChatController2.ts | 4 +- .../chatEdit/notebookChatActionsOverlay.ts | 2 +- 8 files changed, 87 insertions(+), 87 deletions(-) rename src/vs/workbench/contrib/chat/browser/{chatEditorActions.ts => chatEditing/chatEditingEditorActions.ts} (86%) rename src/vs/workbench/contrib/chat/browser/{chatEditorController.ts => chatEditing/chatEditingEditorController.ts} (91%) rename src/vs/workbench/contrib/chat/browser/{chatEditorOverlay.ts => chatEditing/chatEditingEditorOverlay.ts} (86%) diff --git a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts index 9a0043120880..b4c25bbba1e0 100644 --- a/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/accessibility/browser/editorAccessibilityHelp.ts @@ -19,7 +19,7 @@ import { CommentContextKeys } from '../../comments/common/commentContextKeys.js' import { NEW_UNTITLED_FILE_COMMAND_ID } from '../../files/browser/fileConstants.js'; import { IAccessibleViewService, IAccessibleViewContentProvider, AccessibleViewProviderId, IAccessibleViewOptions, AccessibleViewType } from '../../../../platform/accessibility/browser/accessibleView.js'; import { AccessibilityVerbositySettingId } from './accessibilityConfiguration.js'; -import { ctxHasEditorModification, ctxHasRequestInProgress } from '../../chat/browser/chatEditorController.js'; +import { ctxHasEditorModification, ctxHasRequestInProgress } from '../../chat/browser/chatEditing/chatEditingEditorController.js'; export class EditorAccessibilityHelpContribution extends Disposable { static ID: 'editorAccessibilityHelpContribution'; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index 04ed62ec3f73..57a57061aed5 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -19,7 +19,7 @@ import { ChatAgentLocation } from '../../common/chatAgents.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { ChatViewId, EditsViewId, IChatWidgetService } from '../chat.js'; -import { ctxIsGlobalEditingSession } from '../chatEditorController.js'; +import { ctxIsGlobalEditingSession } from '../chatEditing/chatEditingEditorController.js'; import { ChatEditorInput } from '../chatEditorInput.js'; import { ChatViewPane } from '../chatViewPane.js'; import { CHAT_CATEGORY } from './chatActions.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index f570cd3641de..e09865516b6a 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -56,8 +56,8 @@ import './chatAttachmentModel.js'; import { ChatMarkdownAnchorService, IChatMarkdownAnchorService } from './chatContentParts/chatMarkdownAnchorService.js'; import { ChatEditingService } from './chatEditing/chatEditingServiceImpl.js'; import { ChatEditor, IChatEditorOptions } from './chatEditor.js'; -import { registerChatEditorActions } from './chatEditorActions.js'; -import { ChatEditorController } from './chatEditorController.js'; +import { registerChatEditorActions } from './chatEditing/chatEditingEditorActions.js'; +import { ChatEditorController } from './chatEditing/chatEditingEditorController.js'; import { ChatEditorInput, ChatEditorInputSerializer } from './chatEditorInput.js'; import { ChatInputBoxContentProvider } from './chatEdinputInputContentProvider.js'; import { agentSlashCommandToMarkdown, agentToMarkdown } from './chatMarkdownDecorationsRenderer.js'; @@ -80,7 +80,7 @@ import { Extensions, IConfigurationMigrationRegistry } from '../../../common/con import { ChatRelatedFilesContribution } from './contrib/chatInputRelatedFilesContrib.js'; import { ChatQuotasService, ChatQuotasStatusBarEntry, IChatQuotasService } from './chatQuotasService.js'; import { ChatSetupContribution } from './chatSetup.js'; -import { ChatEditorOverlayController } from './chatEditorOverlay.js'; +import { ChatEditorOverlayController } from './chatEditing/chatEditingEditorOverlay.js'; import '../common/promptSyntax/languageFeatures/promptLinkProvider.js'; import { PromptFilesConfig } from '../common/promptSyntax/config.js'; import { BuiltinToolsContribution } from '../common/tools/tools.js'; diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts similarity index 86% rename from src/vs/workbench/contrib/chat/browser/chatEditorActions.ts rename to src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts index 681e61fb730c..072855972ec1 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts @@ -2,24 +2,24 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICodeEditor, isCodeEditor, isDiffEditor } from '../../../../editor/browser/editorBrowser.js'; -import { localize, localize2 } from '../../../../nls.js'; -import { EditorAction2, ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; -import { Codicon } from '../../../../base/common/codicons.js'; -import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js'; -import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; -import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; -import { CHAT_CATEGORY } from './actions/chatActions.js'; -import { ChatEditorController, ctxHasEditorModification, ctxReviewModeEnabled } from './chatEditorController.js'; -import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; -import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js'; -import { ACTIVE_GROUP, IEditorService } from '../../../services/editor/common/editorService.js'; -import { IChatEditingService } from '../common/chatEditingService.js'; -import { ChatContextKeys } from '../common/chatContextKeys.js'; -import { isEqual } from '../../../../base/common/resources.js'; -import { Range } from '../../../../editor/common/core/range.js'; -import { getNotebookEditorFromEditorPane } from '../../notebook/browser/notebookBrowser.js'; -import { ctxNotebookHasEditorModification } from '../../notebook/browser/contrib/chatEdit/notebookChatEditContext.js'; +import { ICodeEditor, isCodeEditor, isDiffEditor } from '../../../../../editor/browser/editorBrowser.js'; +import { localize, localize2 } from '../../../../../nls.js'; +import { EditorAction2, ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js'; +import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; +import { CHAT_CATEGORY } from '../actions/chatActions.js'; +import { ChatEditorController, ctxHasEditorModification, ctxReviewModeEnabled } from './chatEditingEditorController.js'; +import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; +import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js'; +import { ACTIVE_GROUP, IEditorService } from '../../../../services/editor/common/editorService.js'; +import { IChatEditingService } from '../../common/chatEditingService.js'; +import { ChatContextKeys } from '../../common/chatContextKeys.js'; +import { isEqual } from '../../../../../base/common/resources.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { getNotebookEditorFromEditorPane } from '../../../notebook/browser/notebookBrowser.js'; +import { ctxNotebookHasEditorModification } from '../../../notebook/browser/contrib/chatEdit/notebookChatEditContext.js'; abstract class NavigateAction extends Action2 { diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorController.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorController.ts similarity index 91% rename from src/vs/workbench/contrib/chat/browser/chatEditorController.ts rename to src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorController.ts index 13e98f95ec93..3f36f502b92a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorController.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorController.ts @@ -3,41 +3,41 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import './media/chatEditorController.css'; -import { addStandardDisposableListener, getTotalWidth } from '../../../../base/browser/dom.js'; -import { Disposable, DisposableStore, dispose, toDisposable } from '../../../../base/common/lifecycle.js'; -import { autorun, autorunWithStore, derived, IObservable, observableFromEvent, observableFromEventOpts, observableValue } from '../../../../base/common/observable.js'; -import { themeColorFromId } from '../../../../base/common/themables.js'; -import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IOverlayWidgetPositionCoordinates, IViewZone, MouseTargetType } from '../../../../editor/browser/editorBrowser.js'; -import { LineSource, renderLines, RenderOptions } from '../../../../editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; -import { diffAddDecoration, diffDeleteDecoration, diffWholeLineAddDecoration } from '../../../../editor/browser/widget/diffEditor/registrations.contribution.js'; -import { EditorOption, IEditorOptions } from '../../../../editor/common/config/editorOptions.js'; -import { Range } from '../../../../editor/common/core/range.js'; -import { IDocumentDiff } from '../../../../editor/common/diff/documentDiffProvider.js'; -import { IEditorContribution, ScrollType } from '../../../../editor/common/editorCommon.js'; -import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from '../../../../editor/common/model.js'; -import { ModelDecorationOptions } from '../../../../editor/common/model/textModel.js'; -import { InlineDecoration, InlineDecorationType } from '../../../../editor/common/viewModel.js'; -import { localize } from '../../../../nls.js'; -import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; -import { ChatEditingSessionState, IChatEditingService, IModifiedFileEntry, WorkingSetEntryState } from '../common/chatEditingService.js'; -import { Event } from '../../../../base/common/event.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { MenuId } from '../../../../platform/actions/common/actions.js'; -import { IEditorService } from '../../../services/editor/common/editorService.js'; -import { Position } from '../../../../editor/common/core/position.js'; -import { Selection } from '../../../../editor/common/core/selection.js'; -import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; -import { observableCodeEditor } from '../../../../editor/browser/observableCodeEditor.js'; -import { minimapGutterAddedBackground, minimapGutterDeletedBackground, minimapGutterModifiedBackground, overviewRulerAddedForeground, overviewRulerDeletedForeground, overviewRulerModifiedForeground } from '../../scm/common/quickDiff.js'; -import { DetailedLineRangeMapping } from '../../../../editor/common/diff/rangeMapping.js'; -import { isDiffEditorForEntry } from './chatEditing/chatEditing.js'; -import { basename, isEqual } from '../../../../base/common/resources.js'; -import { ChatAgentLocation, IChatAgentService } from '../common/chatAgents.js'; -import { EditorsOrder, IEditorIdentifier, isDiffEditorInput } from '../../../common/editor.js'; -import { ChatEditorOverlayController } from './chatEditorOverlay.js'; -import { IChatService } from '../common/chatService.js'; -import { StableEditorScrollState } from '../../../../editor/browser/stableEditorScroll.js'; +import '../media/chatEditorController.css'; +import { addStandardDisposableListener, getTotalWidth } from '../../../../../base/browser/dom.js'; +import { Disposable, DisposableStore, dispose, toDisposable } from '../../../../../base/common/lifecycle.js'; +import { autorun, autorunWithStore, derived, IObservable, observableFromEvent, observableFromEventOpts, observableValue } from '../../../../../base/common/observable.js'; +import { themeColorFromId } from '../../../../../base/common/themables.js'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IOverlayWidgetPositionCoordinates, IViewZone, MouseTargetType } from '../../../../../editor/browser/editorBrowser.js'; +import { LineSource, renderLines, RenderOptions } from '../../../../../editor/browser/widget/diffEditor/components/diffEditorViewZones/renderLines.js'; +import { diffAddDecoration, diffDeleteDecoration, diffWholeLineAddDecoration } from '../../../../../editor/browser/widget/diffEditor/registrations.contribution.js'; +import { EditorOption, IEditorOptions } from '../../../../../editor/common/config/editorOptions.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { IDocumentDiff } from '../../../../../editor/common/diff/documentDiffProvider.js'; +import { IEditorContribution, ScrollType } from '../../../../../editor/common/editorCommon.js'; +import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane, TrackedRangeStickiness } from '../../../../../editor/common/model.js'; +import { ModelDecorationOptions } from '../../../../../editor/common/model/textModel.js'; +import { InlineDecoration, InlineDecorationType } from '../../../../../editor/common/viewModel.js'; +import { localize } from '../../../../../nls.js'; +import { IContextKey, IContextKeyService, RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js'; +import { ChatEditingSessionState, IChatEditingService, IModifiedFileEntry, WorkingSetEntryState } from '../../common/chatEditingService.js'; +import { Event } from '../../../../../base/common/event.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { MenuId } from '../../../../../platform/actions/common/actions.js'; +import { IEditorService } from '../../../../services/editor/common/editorService.js'; +import { Position } from '../../../../../editor/common/core/position.js'; +import { Selection } from '../../../../../editor/common/core/selection.js'; +import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; +import { observableCodeEditor } from '../../../../../editor/browser/observableCodeEditor.js'; +import { minimapGutterAddedBackground, minimapGutterDeletedBackground, minimapGutterModifiedBackground, overviewRulerAddedForeground, overviewRulerDeletedForeground, overviewRulerModifiedForeground } from '../../../scm/common/quickDiff.js'; +import { DetailedLineRangeMapping } from '../../../../../editor/common/diff/rangeMapping.js'; +import { isDiffEditorForEntry } from './chatEditing.js'; +import { basename, isEqual } from '../../../../../base/common/resources.js'; +import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; +import { EditorsOrder, IEditorIdentifier, isDiffEditorInput } from '../../../../common/editor.js'; +import { ChatEditorOverlayController } from './chatEditingEditorOverlay.js'; +import { IChatService } from '../../common/chatService.js'; +import { StableEditorScrollState } from '../../../../../editor/browser/stableEditorScroll.js'; export const ctxIsGlobalEditingSession = new RawContextKey('chat.isGlobalEditingSession', undefined, localize('chat.ctxEditSessionIsGlobal', "The current editor is part of the global edit session")); export const ctxHasEditorModification = new RawContextKey('chat.hasEditorModifications', undefined, localize('chat.hasEditorModifications', "The current editor contains chat modifications")); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditorOverlay.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorOverlay.ts similarity index 86% rename from src/vs/workbench/contrib/chat/browser/chatEditorOverlay.ts rename to src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorOverlay.ts index acf876c17c4c..0db38aaf8bb9 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditorOverlay.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorOverlay.ts @@ -3,32 +3,32 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; -import { autorun, IObservable, observableFromEvent, observableValue, transaction } from '../../../../base/common/observable.js'; -import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from '../../../../editor/browser/editorBrowser.js'; -import { HiddenItemStrategy, MenuWorkbenchToolBar, WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; -import { IChatEditingSession, IModifiedFileEntry } from '../common/chatEditingService.js'; -import { MenuId } from '../../../../platform/actions/common/actions.js'; -import { ActionViewItem } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; -import { ACTIVE_GROUP, IEditorService } from '../../../services/editor/common/editorService.js'; -import { Range } from '../../../../editor/common/core/range.js'; -import { IActionRunner } from '../../../../base/common/actions.js'; -import { $, addDisposableGenericMouseMoveListener, append, EventLike, reset } from '../../../../base/browser/dom.js'; -import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; -import { ThemeIcon } from '../../../../base/common/themables.js'; -import { Codicon } from '../../../../base/common/codicons.js'; -import { assertType } from '../../../../base/common/types.js'; -import { localize } from '../../../../nls.js'; -import { AcceptAction, navigationBearingFakeActionId, RejectAction } from './chatEditorActions.js'; -import { ChatEditorController } from './chatEditorController.js'; -import './media/chatEditorOverlay.css'; -import { findDiffEditorContainingCodeEditor } from '../../../../editor/browser/widget/diffEditor/commands.js'; -import { IChatService } from '../common/chatService.js'; -import { IEditorContribution } from '../../../../editor/common/editorCommon.js'; -import { rcut } from '../../../../base/common/strings.js'; -import { IHoverService } from '../../../../platform/hover/browser/hover.js'; -import { Lazy } from '../../../../base/common/lazy.js'; +import { DisposableStore, MutableDisposable } from '../../../../../base/common/lifecycle.js'; +import { autorun, IObservable, observableFromEvent, observableValue, transaction } from '../../../../../base/common/observable.js'; +import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from '../../../../../editor/browser/editorBrowser.js'; +import { HiddenItemStrategy, MenuWorkbenchToolBar, WorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { IChatEditingSession, IModifiedFileEntry } from '../../common/chatEditingService.js'; +import { MenuId } from '../../../../../platform/actions/common/actions.js'; +import { ActionViewItem } from '../../../../../base/browser/ui/actionbar/actionViewItems.js'; +import { ACTIVE_GROUP, IEditorService } from '../../../../services/editor/common/editorService.js'; +import { Range } from '../../../../../editor/common/core/range.js'; +import { IActionRunner } from '../../../../../base/common/actions.js'; +import { $, addDisposableGenericMouseMoveListener, append, EventLike, reset } from '../../../../../base/browser/dom.js'; +import { renderIcon } from '../../../../../base/browser/ui/iconLabel/iconLabels.js'; +import { ThemeIcon } from '../../../../../base/common/themables.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { assertType } from '../../../../../base/common/types.js'; +import { localize } from '../../../../../nls.js'; +import { AcceptAction, navigationBearingFakeActionId, RejectAction } from './chatEditingEditorActions.js'; +import { ChatEditorController } from './chatEditingEditorController.js'; +import '../media/chatEditorOverlay.css'; +import { findDiffEditorContainingCodeEditor } from '../../../../../editor/browser/widget/diffEditor/commands.js'; +import { IChatService } from '../../common/chatService.js'; +import { IEditorContribution } from '../../../../../editor/common/editorCommon.js'; +import { rcut } from '../../../../../base/common/strings.js'; +import { IHoverService } from '../../../../../platform/hover/browser/hover.js'; +import { Lazy } from '../../../../../base/common/lazy.js'; class ChatEditorOverlayWidget implements IOverlayWidget { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts index 78b47cf777de..23bb36062f48 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController2.ts @@ -26,8 +26,8 @@ import { IInstantiationService } from '../../../../platform/instantiation/common import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IEditorService } from '../../../services/editor/common/editorService.js'; -import { ctxIsGlobalEditingSession } from '../../chat/browser/chatEditorController.js'; -import { ChatEditorOverlayController } from '../../chat/browser/chatEditorOverlay.js'; +import { ctxIsGlobalEditingSession } from '../../chat/browser/chatEditing/chatEditingEditorController.js'; +import { ChatEditorOverlayController } from '../../chat/browser/chatEditing/chatEditingEditorOverlay.js'; import { IChatWidgetLocationOptions } from '../../chat/browser/chatWidget.js'; import { ChatAgentLocation } from '../../chat/common/chatAgents.js'; import { WorkingSetEntryState } from '../../chat/common/chatEditingService.js'; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts index 8aa7a2c4c9d7..f9c9ed3a545f 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts @@ -18,7 +18,7 @@ import { autorun, autorunWithStore, IObservable, ISettableObservable, observable import { isEqual } from '../../../../../../base/common/resources.js'; import { CellDiffInfo } from '../../diff/notebookDiffViewModel.js'; import { INotebookDeletedCellDecorator } from './notebookCellDecorators.js'; -import { AcceptAction, navigationBearingFakeActionId, RejectAction } from '../../../../chat/browser/chatEditorActions.js'; +import { AcceptAction, navigationBearingFakeActionId, RejectAction } from '../../../../chat/browser/chatEditing/chatEditingEditorActions.js'; export class NotebookChatActionsOverlayController extends Disposable { constructor( From 2b0bc7595ba9190e3a9428b1d821f03f251f5b6e Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:51:18 +0100 Subject: [PATCH 0362/2632] Side By Side NES does not update when model is set (#239621) fixes https://github.com/microsoft/vscode-copilot/issues/12749 --- .../inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts index afd6599ebb9e..cd78afcce01b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts @@ -314,6 +314,7 @@ export class InlineEditsSideBySideDiff extends Disposable implements IInlineEdit private _activeViewZones: string[] = []; private readonly _updatePreviewEditor = derived(reader => { this._editorContainer.readEffect(reader); + this._previewEditorObs.model.read(reader); // update when the model is set // Setting this here explicitly to make sure that the preview editor is // visible when needed, we're also checking that these fields are defined From b1abd2b2737d6084d8010d9e765a3895357324ee Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Tue, 4 Feb 2025 09:09:41 -0800 Subject: [PATCH 0363/2632] create prompt syntax service (#239554) * [service]: II of prompt syntax service * [service]: refactor * [service]: add unit tests * [service]: fix unit tests on Windows machines --- .../contrib/chat/browser/chat.contribution.ts | 4 + .../languageFeatures/promptLinkProvider.ts | 38 +- .../parsers/textModelPromptParser.ts | 4 +- .../service/promptSyntaxService.ts | 76 +++ .../chat/common/promptSyntax/service/types.ts | 31 ++ .../service/promptSyntaxService.test.ts | 527 ++++++++++++++++++ 6 files changed, 645 insertions(+), 35 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/service/promptSyntaxService.ts create mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/service/types.ts create mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptSyntaxService.test.ts diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index e09865516b6a..e6463f990d0f 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -88,6 +88,8 @@ import { IWorkbenchAssignmentService } from '../../../services/assignment/common import { IProductService } from '../../../../platform/product/common/productService.js'; import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { ChatContextKeys } from '../common/chatContextKeys.js'; +import { IPromptSyntaxService } from '../common/promptSyntax/service/types.js'; +import { PromptSyntaxService } from '../common/promptSyntax/service/promptSyntaxService.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -428,3 +430,5 @@ registerSingleton(IChatEditingService, ChatEditingService, InstantiationType.Del registerSingleton(IChatMarkdownAnchorService, ChatMarkdownAnchorService, InstantiationType.Delayed); registerSingleton(ILanguageModelIgnoredFilesService, LanguageModelIgnoredFilesService, InstantiationType.Delayed); registerSingleton(IChatQuotasService, ChatQuotasService, InstantiationType.Delayed); + +registerSingleton(IPromptSyntaxService, PromptSyntaxService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptLinkProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptLinkProvider.ts index 169e320390b4..34bb7f530304 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptLinkProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptLinkProvider.ts @@ -4,61 +4,31 @@ *--------------------------------------------------------------------------------------------*/ import { LANGUAGE_SELECTOR } from '../constants.js'; +import { IPromptSyntaxService } from '../service/types.js'; import { assert } from '../../../../../../base/common/assert.js'; import { ITextModel } from '../../../../../../editor/common/model.js'; import { assertDefined } from '../../../../../../base/common/types.js'; import { Disposable } from '../../../../../../base/common/lifecycle.js'; import { NonPromptSnippetFile } from '../../promptFileReferenceErrors.js'; -import { ObjectCache } from '../../../../../../base/common/objectCache.js'; import { CancellationError } from '../../../../../../base/common/errors.js'; -import { TextModelPromptParser } from '../parsers/textModelPromptParser.js'; import { CancellationToken } from '../../../../../../base/common/cancellation.js'; import { Registry } from '../../../../../../platform/registry/common/platform.js'; import { LifecyclePhase } from '../../../../../services/lifecycle/common/lifecycle.js'; import { ILink, ILinksList, LinkProvider } from '../../../../../../editor/common/languages.js'; import { ILanguageFeaturesService } from '../../../../../../editor/common/services/languageFeatures.js'; -import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../../../common/contributions.js'; /** * Provides link references for prompt files. */ export class PromptLinkProvider extends Disposable implements LinkProvider { - /** - * Cache of text model content prompt parsers. - */ - private readonly parserProvider: ObjectCache; - constructor( - @IInstantiationService private readonly initService: IInstantiationService, + @IPromptSyntaxService private readonly promptSyntaxService: IPromptSyntaxService, @ILanguageFeaturesService private readonly languageService: ILanguageFeaturesService, ) { super(); this._register(this.languageService.linkProvider.register(LANGUAGE_SELECTOR, this)); - this.parserProvider = this._register(new ObjectCache(this.createParser.bind(this))); - } - - /** - * Create new prompt parser instance for the provided text model. - * - * @param model - text model to create the parser for - * @param initService - the instantiation service - */ - private createParser( - model: ITextModel, - ): TextModelPromptParser & { disposed: false } { - const parser: TextModelPromptParser = this.initService.createInstance( - TextModelPromptParser, - model, - [], - ); - - parser.assertNotDisposed( - 'Created prompt parser must not be disposed.', - ); - - return parser; } /** @@ -73,7 +43,7 @@ export class PromptLinkProvider extends Disposable implements LinkProvider { new CancellationError(), ); - const parser = this.parserProvider.get(model); + const parser = this.promptSyntaxService.getParserFor(model); assert( !parser.disposed, 'Prompt parser must not be disposed.', @@ -123,6 +93,6 @@ export class PromptLinkProvider extends Disposable implements LinkProvider { } } -// register the text model prompt decorators provider as a workbench contribution +// register this provider as a workbench contribution Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(PromptLinkProvider, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/textModelPromptParser.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/textModelPromptParser.ts index 08c3ceeb9bf9..6a0e32b50804 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/textModelPromptParser.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/textModelPromptParser.ts @@ -20,7 +20,9 @@ export class TextModelPromptParser extends BasePromptParser this.dispose()); + super(contentsProvider, seenReferences, initService, logService); } diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptSyntaxService.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptSyntaxService.ts new file mode 100644 index 000000000000..b0bc31a93d2f --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/promptSyntaxService.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IPromptSyntaxService } from './types.js'; +import { assert } from '../../../../../../base/common/assert.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { ObjectCache } from '../../../../../../base/common/objectCache.js'; +import { TextModelPromptParser } from '../parsers/textModelPromptParser.js'; +import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js'; + +/** + * Provides prompt syntax services. + */ +export class PromptSyntaxService extends Disposable implements IPromptSyntaxService { + declare readonly _serviceBrand: undefined; + + /** + * Cache of text model content prompt parsers. + */ + private readonly cache: ObjectCache; + + constructor( + @IInstantiationService initService: IInstantiationService, + ) { + super(); + + // the factory function below creates a new prompt parser object + // for the provided model, if no active non-disposed parser exists + this.cache = this._register( + new ObjectCache((model) => { + /** + * Note! When/if shared with "file" prompts, the `seenReferences` array below must be taken into account. + * Otherwise consumers will either see incorrect failing or incorrect successful results, based on their + * use case, timing of their calls to the {@link getParserFor} function, and state of this service. + */ + const parser: TextModelPromptParser = initService.createInstance( + TextModelPromptParser, + model, + [], + ); + + parser.start(); + + // this is a sanity check and the contract of the object cache, + // we must return a non-disposed object from this factory function + parser.assertNotDisposed( + 'Created prompt parser must not be disposed.', + ); + + return parser; + }) + ); + } + + /** + * Gets a prompt syntax parser for the provided text model. + * + * @throws {Error} if: + * - the provided model is disposed + * - newly created parser is disposed immediately on initialization. + * See factory function in the {@link constructor} for more info. + */ + public getParserFor( + model: ITextModel, + ): TextModelPromptParser & { disposed: false } { + assert( + !model.isDisposed(), + 'Cannot create a prompt parser for a disposed model.', + ); + + return this.cache.get(model); + } +} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/service/types.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/service/types.ts new file mode 100644 index 000000000000..211f909e5f97 --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/service/types.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { IDisposable } from '../../../../../../base/common/lifecycle.js'; +import { TextModelPromptParser } from '../parsers/textModelPromptParser.js'; +import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js'; + +/** + * Provides prompt syntax services. + */ +export const IPromptSyntaxService = createDecorator('IPromptSyntaxService'); + +/** + * Provides prompt syntax services. + */ +export interface IPromptSyntaxService extends IDisposable { + readonly _serviceBrand: undefined; + + /** + * Get a prompt syntax parser for the provided text model. + * See {@link TextModelPromptParser} for more info on the parse API. + * + * @throws {Error} If a newly created parser gets immediately disposed. + */ + getParserFor( + model: ITextModel, + ): TextModelPromptParser & { disposed: false }; +} diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptSyntaxService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptSyntaxService.test.ts new file mode 100644 index 000000000000..65c876889897 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptSyntaxService.test.ts @@ -0,0 +1,527 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { URI } from '../../../../../../../base/common/uri.js'; +import { isWindows } from '../../../../../../../base/common/platform.js'; +import { Range } from '../../../../../../../editor/common/core/range.js'; +import { assertDefined } from '../../../../../../../base/common/types.js'; +import { waitRandom } from '../../../../../../../base/test/common/testUtils.js'; +import { IFileService } from '../../../../../../../platform/files/common/files.js'; +import { IPromptFileReference } from '../../../../common/promptSyntax/parsers/types.js'; +import { IPromptSyntaxService } from '../../../../common/promptSyntax/service/types.js'; +import { FileService } from '../../../../../../../platform/files/common/fileService.js'; +import { createTextModel } from '../../../../../../../editor/test/common/testTextModel.js'; +import { ILogService, NullLogService } from '../../../../../../../platform/log/common/log.js'; +import { PromptSyntaxService } from '../../../../common/promptSyntax/service/promptSyntaxService.js'; +import { TextModelPromptParser } from '../../../../common/promptSyntax/parsers/textModelPromptParser.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; +import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; +import { TestInstantiationService } from '../../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; +import { TestConfigurationService } from '../../../../../../../platform/configuration/test/common/testConfigurationService.js'; + +/** + * Helper class to assert the properties of a link. + */ +class ExpectedLink { + constructor( + public readonly uri: URI, + public readonly fullRange: Range, + public readonly linkRange: Range, + ) { } + + /** + * Assert a provided link has the same properties as this object. + */ + public assertEqual(link: IPromptFileReference) { + assert.strictEqual( + link.type, + 'file', + 'Link must have correct type.', + ); + + assert.strictEqual( + link.uri.toString(), + this.uri.toString(), + 'Link must have correct URI.', + ); + + assert( + this.fullRange.equalsRange(link.range), + `Full range must be '${this.fullRange}', got '${link.range}'.`, + ); + + assertDefined( + link.linkRange, + 'Link must have a link range.', + ); + + assert( + this.linkRange.equalsRange(link.linkRange), + `Link range must be '${this.linkRange}', got '${link.linkRange}'.`, + ); + } +} + +/** + * Asserts that provided links are equal to the expected links. + * @param links Links to assert. + * @param expectedLinks Expected links to compare against. + */ +const assertLinks = ( + links: readonly IPromptFileReference[], + expectedLinks: readonly ExpectedLink[], +) => { + for (let i = 0; i < links.length; i++) { + try { + expectedLinks[i].assertEqual(links[i]); + } catch (error) { + throw new Error(`link#${i}: ${error}`); + } + } + + assert.strictEqual( + links.length, + expectedLinks.length, + `Links count must be correct.`, + ); +}; + +/** + * Creates cross-platform URI. On Windows, absolute paths + * are prefixed with the disk name. + */ +const createURI = (linkPath: string): URI => { + if (isWindows && linkPath.startsWith('/')) { + return URI.file('/d:' + linkPath); + } + + return URI.file(linkPath); +}; + +suite('PromptSyntaxService', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + let service: IPromptSyntaxService; + let instantiationService: TestInstantiationService; + + setup(async () => { + instantiationService = disposables.add(new TestInstantiationService()); + instantiationService.stub(ILogService, new NullLogService()); + instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IFileService, new TestConfigurationService()); + instantiationService.stub(IFileService, disposables.add(instantiationService.createInstance(FileService))); + + service = disposables.add(instantiationService.createInstance(PromptSyntaxService)); + }); + + suite('getParserFor', () => { + test('provides cached parser instance', async () => { + const langId = 'fooLang'; + + /** + * Create a text model, get a parser for it, and perform basic assertions. + */ + + const model1 = disposables.add(createTextModel( + 'test1\n\t#file:./file.md\n\n\n [bin file](/root/tmp.bin)\t\n', + langId, + undefined, + createURI('/Users/vscode/repos/test/file1.txt'), + )); + + const parser1 = service.getParserFor(model1); + assert.strictEqual( + parser1.uri.toString(), + model1.uri.toString(), + 'Must create parser1 with the correct URI.', + ); + + assert( + !parser1.disposed, + 'Parser1 must not be disposed.', + ); + + assert( + parser1 instanceof TextModelPromptParser, + 'Parser1 must be an instance of TextModelPromptParser.', + ); + + /** + * Validate that all links of the model are correctly parsed. + */ + + await parser1.settled(); + assertLinks( + parser1.allReferences, + [ + new ExpectedLink( + createURI('/Users/vscode/repos/test/file.md'), + new Range(2, 2, 2, 2 + 15), + new Range(2, 8, 2, 8 + 9), + ), + new ExpectedLink( + createURI('/root/tmp.bin'), + new Range(5, 4, 5, 4 + 25), + new Range(5, 15, 5, 15 + 13), + ), + ], + ); + + // wait for some random amount of time + await waitRandom(5); + + /** + * Next, get parser for the same exact model and + * validate that the same cached object is returned. + */ + + // get the same parser again, the call must return the same object + const parser1_1 = service.getParserFor(model1); + assert.strictEqual( + parser1, + parser1_1, + 'Must return the same parser object.', + ); + + assert.strictEqual( + parser1_1.uri.toString(), + model1.uri.toString(), + 'Must create parser1_1 with the correct URI.', + ); + + /** + * Get parser for a different model and perform basic assertions. + */ + + const model2 = disposables.add(createTextModel( + 'some text #file:/absolute/path.txt \t\ntest-text2', + langId, + undefined, + createURI('/Users/vscode/repos/test/some-folder/file.md'), + )); + + // wait for some random amount of time + await waitRandom(5); + + const parser2 = service.getParserFor(model2); + + assert.strictEqual( + parser2.uri.toString(), + model2.uri.toString(), + 'Must create parser2 with the correct URI.', + ); + + assert( + !parser2.disposed, + 'Parser2 must not be disposed.', + ); + + assert( + parser2 instanceof TextModelPromptParser, + 'Parser2 must be an instance of TextModelPromptParser.', + ); + + assert( + !parser2.disposed, + 'Parser2 must not be disposed.', + ); + + assert( + !parser1.disposed, + 'Parser1 must not be disposed.', + ); + + assert( + !parser1_1.disposed, + 'Parser1_1 must not be disposed.', + ); + + /** + * Validate that all links of the model 2 are correctly parsed. + */ + + await parser2.settled(); + + assert.notStrictEqual( + parser1.uri.toString(), + parser2.uri.toString(), + 'Parser2 must have its own URI.', + ); + + assertLinks( + parser2.allReferences, + [ + new ExpectedLink( + createURI('/absolute/path.txt'), + new Range(1, 11, 1, 11 + 24), + new Range(1, 17, 1, 17 + 18), + ), + ], + ); + + /** + * Validate the first parser was not affected by the presence + * of the second parser. + */ + + await parser1_1.settled(); + + // parser1_1 has the same exact links as before + assertLinks( + parser1_1.allReferences, + [ + new ExpectedLink( + createURI('/Users/vscode/repos/test/file.md'), + new Range(2, 2, 2, 2 + 15), + new Range(2, 8, 2, 8 + 9), + ), + new ExpectedLink( + createURI('/root/tmp.bin'), + new Range(5, 4, 5, 4 + 25), + new Range(5, 15, 5, 15 + 13), + ), + ], + ); + + // wait for some random amount of time + await waitRandom(5); + + /** + * Dispose the first parser, perform basic validations, and confirm + * that the second parser is not affected by the disposal of the first one. + */ + parser1.dispose(); + + assert( + parser1.disposed, + 'Parser1 must be disposed.', + ); + + assert( + parser1_1.disposed, + 'Parser1_1 must be disposed.', + ); + + assert( + !parser2.disposed, + 'Parser2 must not be disposed.', + ); + + + /** + * Get parser for the first model again. Confirm that we get + * a new non-disposed parser object back with correct properties. + */ + + const parser1_2 = service.getParserFor(model1); + + assert( + !parser1_2.disposed, + 'Parser1_2 must not be disposed.', + ); + + assert.notStrictEqual( + parser1_2, + parser1, + 'Must create a new parser object for the model1.', + ); + + assert.strictEqual( + parser1_2.uri.toString(), + model1.uri.toString(), + 'Must create parser1_2 with the correct URI.', + ); + + /** + * Validate that the contents of the second parser did not change. + */ + + await parser1_2.settled(); + + // parser1_2 must have the same exact links as before + assertLinks( + parser1_2.allReferences, + [ + new ExpectedLink( + createURI('/Users/vscode/repos/test/file.md'), + new Range(2, 2, 2, 2 + 15), + new Range(2, 8, 2, 8 + 9), + ), + new ExpectedLink( + createURI('/root/tmp.bin'), + new Range(5, 4, 5, 4 + 25), + new Range(5, 15, 5, 15 + 13), + ), + ], + ); + + // wait for some random amount of time + await waitRandom(5); + + /** + * This time dispose model of the second parser instead of + * the parser itself. Validate that the parser is disposed too, but + * the newly created first parser is not affected. + */ + + // dispose the `model` of the second parser now + model2.dispose(); + + // assert that the parser is also disposed + assert( + parser2.disposed, + 'Parser2 must be disposed.', + ); + + // sanity check that the other parser is not affected + assert( + !parser1_2.disposed, + 'Parser1_2 must not be disposed.', + ); + + /** + * Create a new second parser with new model - we cannot use + * the old one because it was disposed. This new model also has + * a different second link. + */ + + // we cannot use the same model since it was already disposed + const model2_1 = disposables.add(createTextModel( + 'some text #file:/absolute/path.txt \n [caption](.copilot/prompts/test.prompt.md)\t\n\t\n more text', + langId, + undefined, + createURI('/Users/vscode/repos/test/some-folder/file.md'), + )); + const parser2_1 = service.getParserFor(model2_1); + + assert( + !parser2_1.disposed, + 'Parser2_1 must not be disposed.', + ); + + assert.notStrictEqual( + parser2_1, + parser2, + 'Parser2_1 must be a new object.', + ); + + assert.strictEqual( + parser2_1.uri.toString(), + model2.uri.toString(), + 'Must create parser2_1 with the correct URI.', + ); + + /** + * Validate that new model2 contents are parsed correctly. + */ + + await parser2_1.settled(); + + // parser2_1 must have 2 links now + assertLinks( + parser2_1.allReferences, + [ + // the first link didn't change + new ExpectedLink( + createURI('/absolute/path.txt'), + new Range(1, 11, 1, 11 + 24), + new Range(1, 17, 1, 17 + 18), + ), + // the second link is new + new ExpectedLink( + createURI('/Users/vscode/repos/test/some-folder/.copilot/prompts/test.prompt.md'), + new Range(2, 2, 2, 2 + 42), + new Range(2, 12, 2, 12 + 31), + ), + ], + ); + }); + + test('auto-updated on model changes', async () => { + const langId = 'bazLang'; + + const model = disposables.add(createTextModel( + ' \t #file:../file.md\ntest1\n\t\n [another file](/Users/root/tmp/file2.txt)\t\n', + langId, + undefined, + createURI('/repos/test/file1.txt'), + )); + + const parser = service.getParserFor(model); + + // sanity checks + assert( + !parser.disposed, + 'Parser must not be disposed.', + ); + assert( + parser instanceof TextModelPromptParser, + 'Parser must be an instance of TextModelPromptParser.', + ); + + await parser.settled(); + + assertLinks( + parser.allReferences, + [ + new ExpectedLink( + createURI('/repos/file.md'), + new Range(1, 4, 1, 4 + 16), + new Range(1, 10, 1, 10 + 10), + ), + new ExpectedLink( + createURI('/Users/root/tmp/file2.txt'), + new Range(4, 3, 4, 3 + 41), + new Range(4, 18, 4, 18 + 25), + ), + ], + ); + + model.applyEdits([ + { + range: new Range(4, 18, 4, 18 + 25), + text: '/Users/root/tmp/file3.txt', + }, + ]); + + await parser.settled(); + + assertLinks( + parser.allReferences, + [ + // link1 didn't change + new ExpectedLink( + createURI('/repos/file.md'), + new Range(1, 4, 1, 4 + 16), + new Range(1, 10, 1, 10 + 10), + ), + // link2 changed in the file name only + new ExpectedLink( + createURI('/Users/root/tmp/file3.txt'), + new Range(4, 3, 4, 3 + 41), + new Range(4, 18, 4, 18 + 25), + ), + ], + ); + }); + + test('throws if disposed model provided', async function () { + const model = disposables.add(createTextModel( + 'test1\ntest2\n\ntest3\t\n', + 'barLang', + undefined, + URI.parse('./github/prompts/file.prompt.md'), + )); + + // dispose the model before using it + model.dispose(); + + assert.throws(() => { + service.getParserFor(model); + }, 'Cannot create a prompt parser for a disposed model.'); + }); + }); +}); From a58d48f1812d61033bc7fe8a0e5d4eb79705405d Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 4 Feb 2025 09:16:08 -0800 Subject: [PATCH 0364/2632] Ensure agent experiment defaults to not disabled (#239625) * Safer fix for 239274 (#239511) * Revert "fix config-context-key handling when config isn't known yet (#239294)" This reverts commit d231e6ca13225ff4cbd715566263dc5f2e5792d6. * Ensure agentModeDisallowed key always changes when setting is registered * Ensure defaults to not disabled --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 7 ++++--- src/vs/workbench/contrib/chat/common/chatContextKeys.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index e6463f990d0f..878728cea293 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -234,10 +234,11 @@ class ChatAgentSettingContribution implements IWorkbenchContribution { } const expDisabledKey = ChatContextKeys.Editing.agentModeDisallowed.bindTo(contextKeyService); - experimentService.getTreatment('chatAgentEnabled').then(value => { - if (value) { + experimentService.getTreatment('chatAgentEnabled').then(enabled => { + if (enabled) { this.registerSetting(); - } else if (value === false) { + expDisabledKey.set(false); + } else if (enabled === false) { this.deregisterSetting(); expDisabledKey.set(true); } diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 4a2798b9f0eb..591bbdcad92c 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -83,6 +83,6 @@ export namespace ChatContextKeys { export const Editing = { hasToolsAgent: new RawContextKey('chatHasToolsAgent', false, { type: 'boolean', description: localize('chatEditingHasToolsAgent', "True when a tools agent is registered.") }), agentMode: new RawContextKey('chatAgentMode', false, { type: 'boolean', description: localize('chatEditingAgentMode', "True when edits is in agent mode.") }), - agentModeDisallowed: new RawContextKey('chatAgentModeDisallowed', false, { type: 'boolean', description: localize('chatAgentModeDisallowed', "True when agent mode is not allowed.") }), // experiment-driven disablement + agentModeDisallowed: new RawContextKey('chatAgentModeDisallowed', undefined, { type: 'boolean', description: localize('chatAgentModeDisallowed', "True when agent mode is not allowed.") }), // experiment-driven disablement }; } From 30dbfbb6838d4460fb43ffbc4abaaa57647cd77c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 4 Feb 2025 19:31:22 +0100 Subject: [PATCH 0365/2632] Provide a way to reveal the entire URI in an 'Allow extension to open URI" dialog (fix #239272) (#239527) --- .../extensions/browser/extensionUrlHandler.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts index d1a25424a4e0..ee1e81a8f15c 100644 --- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts @@ -26,6 +26,7 @@ import { mainWindow } from '../../../../base/browser/window.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { isCancellationError } from '../../../../base/common/errors.js'; import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { MarkdownString } from '../../../../base/common/htmlContent.js'; const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000; @@ -197,10 +198,11 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { || this.didUserTrustExtension(ExtensionIdentifier.toKey(extensionId)); if (!trusted) { - let uriString = uri.toString(false); + const uriString = uri.toString(false); + let uriLabel = uriString; - if (uriString.length > 40) { - uriString = `${uriString.substring(0, 30)}...${uriString.substring(uriString.length - 5)}`; + if (uriLabel.length > 40) { + uriLabel = `${uriLabel.substring(0, 30)}...${uriLabel.substring(uriLabel.length - 5)}`; } const result = await this.dialogService.confirm({ @@ -208,8 +210,12 @@ class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { checkbox: { label: localize('rememberConfirmUrl', "Do not ask me again for this extension"), }, - detail: uriString, - primaryButton: localize({ key: 'open', comment: ['&& denotes a mnemonic'] }, "&&Open") + primaryButton: localize({ key: 'open', comment: ['&& denotes a mnemonic'] }, "&&Open"), + custom: { + markdownDetails: [{ + markdown: new MarkdownString(`
${uriLabel}
`, { supportHtml: true }), + }] + } }); if (!result.confirmed) { From 12acdeaa357f2b1d821d76673b44f897a2a3ef58 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Tue, 4 Feb 2025 20:19:29 +0100 Subject: [PATCH 0366/2632] dnd for references widget (#239637) fixes https://github.com/microsoft/vscode/issues/239636 --- .../browser/peek/referencesWidget.ts | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts index fce167fb08fa..9581b5cf6ff6 100644 --- a/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/browser/peek/referencesWidget.ts @@ -34,6 +34,10 @@ import { ILabelService } from '../../../../../platform/label/common/label.js'; import { IWorkbenchAsyncDataTreeOptions, WorkbenchAsyncDataTree } from '../../../../../platform/list/browser/listService.js'; import { IColorTheme, IThemeService } from '../../../../../platform/theme/common/themeService.js'; import { FileReferences, OneReference, ReferencesModel } from '../referencesModel.js'; +import { ITreeDragAndDrop, ITreeDragOverReaction } from '../../../../../base/browser/ui/tree/tree.js'; +import { DataTransfers, IDragAndDropData } from '../../../../../base/browser/dnd.js'; +import { ElementsDragAndDropData } from '../../../../../base/browser/ui/list/listView.js'; +import { withSelection } from '../../../../../platform/opener/common/opener.js'; class DecorationsManager implements IDisposable { @@ -188,6 +192,51 @@ export interface SelectionEvent { class ReferencesTree extends WorkbenchAsyncDataTree { } +class ReferencesDragAndDrop implements ITreeDragAndDrop { + + private readonly disposables = new DisposableStore(); + + constructor(@ILabelService private readonly labelService: ILabelService) { } + + getDragURI(element: TreeElement): string | null { + if (element instanceof FileReferences) { + return element.uri.toString(); + } else if (element instanceof OneReference) { + return withSelection(element.uri, element.range).toString(); + } + return null; + } + + getDragLabel(elements: TreeElement[]): string | undefined { + if (elements.length === 0) { + return undefined; + } + const labels = elements.map(e => this.labelService.getUriBasenameLabel(e.uri)); + return labels.join(', '); + } + + onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { + if (!originalEvent.dataTransfer) { + return; + } + + const elements = (data as ElementsDragAndDropData).elements; + const resources = elements.map(e => this.getDragURI(e)).filter(Boolean); + + if (resources.length) { + // Apply resources as resource-list + originalEvent.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify(resources)); + + // Also add as plain text for outside consumers + originalEvent.dataTransfer.setData(DataTransfers.TEXT, resources.join('\n')); + } + } + + onDragOver(): boolean | ITreeDragOverReaction { return false; } + drop(): void { } + dispose(): void { this.disposables.dispose(); } +} + /** * ZoneWidget that is shown inside the editor */ @@ -328,7 +377,8 @@ export class ReferenceWidget extends peekView.PeekViewWidget { selectionNavigation: true, overrideStyles: { listBackground: peekView.peekViewResultsBackground - } + }, + dnd: this._instantiationService.createInstance(ReferencesDragAndDrop) }; if (this._defaultTreeKeyboardSupport) { // the tree will consume `Escape` and prevent the widget from closing From fe6c1b1d12a414093b690e69e6c15d143e992f91 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Tue, 4 Feb 2025 11:54:41 -0800 Subject: [PATCH 0367/2632] Add builtin #selection variable (#239638) * Add builtin #selection variable Acts as a helper to add the current selection as a reference * Simplify variable completions registration --- .../chat/browser/actions/chatActions.ts | 2 +- .../browser/actions/chatContextActions.ts | 2 +- .../contrib/chat/browser/chat.contribution.ts | 2 +- .../contrib/chat/browser/chatVariables.ts | 7 +- .../browser/contrib/chatInputCompletions.ts | 174 ++++++++++++------ .../contrib/chat/common/chatVariables.ts | 2 +- 6 files changed, 124 insertions(+), 65 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index d7532a3514b8..b7177da3eb9b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -139,7 +139,7 @@ class OpenChatGlobalAction extends Action2 { } } if (opts?.variableIds && opts.variableIds.length > 0) { - const actualVariables = chatVariablesService.getVariables(ChatAgentLocation.Panel); + const actualVariables = chatVariablesService.getVariables(); for (const actualVariable of actualVariables) { if (opts.variableIds.includes(actualVariable.id)) { chatWidget.attachmentModel.addContext({ diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index cdd4135f32b5..f091b1600a3c 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -677,7 +677,7 @@ export class AttachContextAction extends Action2 { const slowSupported = usedAgent ? usedAgent.agent.metadata.supportsSlowVariables : true; const quickPickItems: IAttachmentQuickPickItem[] = []; if (!context || !context.showFilesOnly) { - for (const variable of chatVariablesService.getVariables(widget.location)) { + for (const variable of chatVariablesService.getVariables()) { if (variable.fullName && (!variable.isSlow || slowSupported)) { quickPickItems.push({ kind: 'variable', diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 878728cea293..824ba88a2f45 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -349,7 +349,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { } const variables = [ - ...chatVariablesService.getVariables(ChatAgentLocation.Panel), + ...chatVariablesService.getVariables(), { name: 'file', description: nls.localize('file', "Choose a file in the workspace") } ]; const variableText = variables diff --git a/src/vs/workbench/contrib/chat/browser/chatVariables.ts b/src/vs/workbench/contrib/chat/browser/chatVariables.ts index 4ab91fa76d76..7d92c1e31d99 100644 --- a/src/vs/workbench/contrib/chat/browser/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/browser/chatVariables.ts @@ -123,12 +123,9 @@ export class ChatVariablesService implements IChatVariablesService { return this._resolver.get(name.toLowerCase())?.data; } - getVariables(location: ChatAgentLocation): Iterable> { + getVariables(): Iterable> { const all = Iterable.map(this._resolver.values(), data => data.data); - return Iterable.filter(all, data => { - // TODO@jrieken this is improper and should be know from the variable registeration data - return location !== ChatAgentLocation.Editor || !new Set(['selection', 'editor']).has(data.name); - }); + return all; } getDynamicVariables(sessionId: string): ReadonlyArray { diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index 717ed7e796a4..9ab466c60a44 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -11,10 +11,11 @@ import { Disposable } from '../../../../../base/common/lifecycle.js'; import { ResourceSet } from '../../../../../base/common/map.js'; import { URI } from '../../../../../base/common/uri.js'; import { generateUuid } from '../../../../../base/common/uuid.js'; +import { isCodeEditor } from '../../../../../editor/browser/editorBrowser.js'; import { Position } from '../../../../../editor/common/core/position.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { IWordAtPosition, getWordAtText } from '../../../../../editor/common/core/wordHelper.js'; -import { CompletionContext, CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList, DocumentSymbol, Location, SymbolKind, SymbolKinds } from '../../../../../editor/common/languages.js'; +import { CompletionContext, CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList, DocumentSymbol, Location, ProviderResult, SymbolKind, SymbolKinds } from '../../../../../editor/common/languages.js'; import { ITextModel } from '../../../../../editor/common/model.js'; import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js'; import { IOutlineModelService } from '../../../../../editor/contrib/documentSymbols/browser/outlineModel.js'; @@ -27,6 +28,7 @@ import { ILabelService } from '../../../../../platform/label/common/label.js'; import { Registry } from '../../../../../platform/registry/common/platform.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../../common/contributions.js'; +import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { IHistoryService } from '../../../../services/history/common/history.js'; import { LifecyclePhase } from '../../../../services/lifecycle/common/lifecycle.js'; import { QueryBuilder } from '../../../../services/search/common/queryBuilder.js'; @@ -436,6 +438,14 @@ class ReferenceArgument { ) { } } +interface IVariableCompletionsDetails { + model: ITextModel; + position: Position; + context: CompletionContext; + widget: IChatWidget; + range: IChatCompletionRangeResult; +} + class BuiltinDynamicCompletions extends Disposable { private static readonly addReferenceCommand = '_addReferenceCmd'; private static readonly VariableNameDef = new RegExp(`${chatVariableLeader}\\w*`, 'g'); // MUST be using `g`-flag @@ -452,82 +462,134 @@ class BuiltinDynamicCompletions extends Disposable { @IChatEditingService private readonly _chatEditingService: IChatEditingService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IOutlineModelService private readonly outlineService: IOutlineModelService, + @IEditorService private readonly editorService: IEditorService, ) { super(); // File completions - this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { - _debugDisplayName: 'chatDynamicFileCompletions', - triggerCharacters: [chatVariableLeader], - provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { - const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); - if (!widget || !widget.supportsFileReferences) { - return null; - } + this.registerVariableCompletions('file', async ({ widget, range, position, model }, token) => { + if (!widget.supportsFileReferences) { + return null; + } - const result: CompletionList = { suggestions: [] }; - const range = computeCompletionRanges(model, position, BuiltinDynamicCompletions.VariableNameDef, true); + const result: CompletionList = { suggestions: [] }; + + const afterRange = new Range(position.lineNumber, range.replace.startColumn, position.lineNumber, range.replace.startColumn + '#file:'.length); + result.suggestions.push({ + label: `${chatVariableLeader}file`, + insertText: `${chatVariableLeader}file:`, + documentation: localize('pickFileLabel', "Pick a file"), + range, + kind: CompletionItemKind.Text, + command: { id: SelectAndInsertFileAction.ID, title: SelectAndInsertFileAction.ID, arguments: [{ widget, range: afterRange }] }, + sortText: 'z' + }); - if (range) { - const afterRange = new Range(position.lineNumber, range.replace.startColumn, position.lineNumber, range.replace.startColumn + '#file:'.length); - result.suggestions.push({ - label: `${chatVariableLeader}file`, - insertText: `${chatVariableLeader}file:`, - documentation: localize('pickFileLabel', "Pick a file"), - range, - kind: CompletionItemKind.Text, - command: { id: SelectAndInsertFileAction.ID, title: SelectAndInsertFileAction.ID, arguments: [{ widget, range: afterRange }] }, - sortText: 'z' - }); - } + const range2 = computeCompletionRanges(model, position, new RegExp(`${chatVariableLeader}[^\\s]*`, 'g'), true); + if (range2) { + await this.addFileEntries(widget, result, range2, token); + } - const range2 = computeCompletionRanges(model, position, new RegExp(`${chatVariableLeader}[^\\s]*`, 'g'), true); - if (range2) { - await this.addFileEntries(widget, result, range2, token); - } + return result; + }); - return result; + // Selection completion + this.registerVariableCompletions('selection', ({ widget, range }, token) => { + if (!widget.supportsFileReferences) { + return; } - })); + + if (widget.location === ChatAgentLocation.Editor) { + return; + } + + const active = this.editorService.activeTextEditorControl; + if (!isCodeEditor(active)) { + return; + } + + const currentResource = active.getModel()?.uri; + const currentSelection = active.getSelection(); + if (!currentSelection || !currentResource || currentSelection.isEmpty()) { + return; + } + + const basename = this.labelService.getUriBasenameLabel(currentResource); + const text = `${chatVariableLeader}file:${basename}:${currentSelection.startLineNumber}-${currentSelection.endLineNumber}`; + const fullRangeText = `:${currentSelection.startLineNumber}:${currentSelection.startColumn}-${currentSelection.endLineNumber}:${currentSelection.endColumn}`; + const description = this.labelService.getUriLabel(currentResource, { relative: true }) + fullRangeText; + + const result: CompletionList = { suggestions: [] }; + result.suggestions.push({ + label: { label: `${chatVariableLeader}selection`, description }, + filterText: `${chatVariableLeader}selection`, + insertText: range.varWord?.endColumn === range.replace.endColumn ? `${text} ` : text, + range, + kind: CompletionItemKind.Text, + sortText: 'z', + command: { + id: BuiltinDynamicCompletions.addReferenceCommand, title: '', arguments: [new ReferenceArgument(widget, { + id: 'vscode.file', + prefix: 'file', + isFile: true, + range: { startLineNumber: range.replace.startLineNumber, startColumn: range.replace.startColumn, endLineNumber: range.replace.endLineNumber, endColumn: range.replace.startColumn + text.length }, + data: { range: currentSelection, uri: currentResource } satisfies Location + })] + } + }); + return result; + }); // Symbol completions + this.registerVariableCompletions('symbol', ({ widget, range, position, model }, token) => { + if (!widget.supportsFileReferences) { + return null; + } + + const result: CompletionList = { suggestions: [] }; + + const afterRangeSym = new Range(position.lineNumber, range.replace.startColumn, position.lineNumber, range.replace.startColumn + '#sym:'.length); + result.suggestions.push({ + label: `${chatVariableLeader}sym`, + insertText: `${chatVariableLeader}sym:`, + documentation: localize('pickSymbolLabel', "Pick a symbol"), + range, + kind: CompletionItemKind.Text, + command: { id: SelectAndInsertSymAction.ID, title: SelectAndInsertSymAction.ID, arguments: [{ widget, range: afterRangeSym }] }, + sortText: 'z' + }); + + const range2 = computeCompletionRanges(model, position, new RegExp(`${chatVariableLeader}[^\\s]*`, 'g'), true); + if (range2) { + this.addSymbolEntries(widget, result, range2, token); + } + + return result; + }); + + this._register(CommandsRegistry.registerCommand(BuiltinDynamicCompletions.addReferenceCommand, (_services, arg) => this.cmdAddReference(arg))); + + this.queryBuilder = this.instantiationService.createInstance(QueryBuilder); + } + + private registerVariableCompletions(debugName: string, provider: (details: IVariableCompletionsDetails, token: CancellationToken) => ProviderResult) { this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { - _debugDisplayName: 'chatDynamicSymbolCompletions', + _debugDisplayName: `chatVarCompletions-${debugName}`, triggerCharacters: [chatVariableLeader], - provideCompletionItems: async (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken) => { + provideCompletionItems: async (model: ITextModel, position: Position, context: CompletionContext, token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); - if (!widget || !widget.supportsFileReferences) { - return null; + if (!widget) { + return; } - const result: CompletionList = { suggestions: [] }; const range = computeCompletionRanges(model, position, BuiltinDynamicCompletions.VariableNameDef, true); - if (range) { - const afterRangeSym = new Range(position.lineNumber, range.replace.startColumn, position.lineNumber, range.replace.startColumn + '#sym:'.length); - result.suggestions.push({ - label: `${chatVariableLeader}sym`, - insertText: `${chatVariableLeader}sym:`, - documentation: localize('pickSymbolLabel', "Pick a symbol"), - range, - kind: CompletionItemKind.Text, - command: { id: SelectAndInsertSymAction.ID, title: SelectAndInsertSymAction.ID, arguments: [{ widget, range: afterRangeSym }] }, - sortText: 'z' - }); - } - - const range2 = computeCompletionRanges(model, position, new RegExp(`${chatVariableLeader}[^\\s]*`, 'g'), true); - if (range2) { - this.addSymbolEntries(widget, result, range2, token); + return provider({ model, position, widget, range, context }, token); } - return result; + return; } })); - - this._register(CommandsRegistry.registerCommand(BuiltinDynamicCompletions.addReferenceCommand, (_services, arg) => this.cmdAddReference(arg))); - - this.queryBuilder = this.instantiationService.createInstance(QueryBuilder); } private cacheKey?: { key: string; time: number }; @@ -797,7 +859,7 @@ class VariableCompletions extends Disposable { const usedVariables = widget.parsedInput.parts.filter((p): p is ChatRequestVariablePart => p instanceof ChatRequestVariablePart); const usedVariableNames = new Set(usedVariables.map(v => v.variableName)); - const variableItems = Array.from(this.chatVariablesService.getVariables(widget.location)) + const variableItems = Array.from(this.chatVariablesService.getVariables()) // This doesn't look at dynamic variables like `file`, where multiple makes sense. .filter(v => !usedVariableNames.has(v.name)) .filter(v => !v.isSlow || slowSupported) diff --git a/src/vs/workbench/contrib/chat/common/chatVariables.ts b/src/vs/workbench/contrib/chat/common/chatVariables.ts index 1bd1db9c3964..170a2e133671 100644 --- a/src/vs/workbench/contrib/chat/common/chatVariables.ts +++ b/src/vs/workbench/contrib/chat/common/chatVariables.ts @@ -43,7 +43,7 @@ export interface IChatVariablesService { registerVariable(data: IChatVariableData, resolver: IChatVariableResolver): IDisposable; hasVariable(name: string): boolean; getVariable(name: string): IChatVariableData | undefined; - getVariables(location: ChatAgentLocation): Iterable>; + getVariables(): Iterable>; getDynamicVariables(sessionId: string): ReadonlyArray; // should be its own service? attachContext(name: string, value: string | URI | Location | unknown, location: ChatAgentLocation): void; From a7a2fff97af332e1a73c4f036d450c4761ede207 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Tue, 4 Feb 2025 12:39:10 -0800 Subject: [PATCH 0368/2632] Fix video layout in walkthroughs (#239639) --- .../browser/gettingStartedDetailsRenderer.ts | 39 ++----------------- .../browser/media/gettingStarted.css | 15 +++++-- 2 files changed, 15 insertions(+), 39 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts index 8fc561417acb..d2be9cd03095 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts @@ -213,46 +213,15 @@ export class GettingStartedDetailsRenderer { video { max-width: 100%; max-height: 100%; - object-fit: cover; - } - vertically-centered { - display: flex; - justify-content: center; /* Centers horizontally */ - align-items: center; /* Centers vertically */ - height: 100vh; /* Added missing semicolon */ + object-fit: cover; } - - - + - - `; } diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css index 3edd78f86400..cd312dfbe331 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css @@ -530,7 +530,7 @@ .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .gettingStartedDetailsContent { height: 100%; - max-width: 1200px; + max-width: 80%; margin: 0 auto; padding: 0 32px; display: grid; @@ -580,10 +580,12 @@ } .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .gettingStartedDetailsContent.video > .getting-started-media { - grid-area: title-start / media-start / steps-end / media-end; - align-self: unset; + grid-area: steps-start / media-start / footer-start / media-end; + align-self: self-start; display: flex; - justify-content: center; + justify-content:center ; + height: 100%; + width: 100%; } .monaco-workbench .part.editor > .content .gettingStartedContainer.width-semi-constrained .gettingStartedSlideDetails .gettingStartedDetailsContent.video > .getting-started-media { @@ -613,6 +615,11 @@ justify-content: center; } +.monaco-workbench .part.editor > .content .gettingStartedContainer.width-constrained .gettingStartedSlideDetails .gettingStartedDetailsContent.image > .getting-started-media, +.monaco-workbench .part.editor > .content .gettingStartedContainer.width-constrained .gettingStartedSlideDetails .gettingStartedDetailsContent.video > .getting-started-media { + display: none; +} + .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .gettingStartedDetailsContent > .getting-started-media > video { max-width: 100%; max-height: 100%; From 1c931b181d6922ddc1eac2469117fba2c500da07 Mon Sep 17 00:00:00 2001 From: John Murray Date: Tue, 4 Feb 2025 21:14:23 +0000 Subject: [PATCH 0369/2632] Supply multiselects to `scm/resourceGroup/context` menu commands (fix #92337) (#192172) * Supply multiselects to `scm/resourceGroup/context` menu commands (fixes #92337) * Fix faulty merge * Pull request feedback --------- Co-authored-by: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> --- .../contrib/scm/browser/scmViewPane.ts | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index c7c7b4c68ed6..cec11c8fff97 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -432,6 +432,7 @@ class ResourceGroupRenderer implements ICompressibleTreeRenderer (ISCMResource | IResourceNode)[]) { + constructor(private getSelectedResources: () => (ISCMResourceGroup | ISCMResource | IResourceNode)[]) { super(); } - protected override async runAction(action: IAction, context: ISCMResource | IResourceNode): Promise { + protected override async runAction(action: IAction, context: ISCMResourceGroup | ISCMResource | IResourceNode): Promise { if (!(action instanceof MenuItemAction)) { return super.runAction(action, context); } - const selection = this.getSelectedResources(); + const isContextResourceGroup = isSCMResourceGroup(context); + const selection = this.getSelectedResources().filter(r => isSCMResourceGroup(r) === isContextResourceGroup); + const contextIsSelected = selection.some(s => s === context); const actualContext = contextIsSelected ? selection : [context]; const args = actualContext.map(e => ResourceTree.isResourceNode(e) ? ResourceTree.collect(e) : [e]).flat(); @@ -2326,7 +2332,7 @@ export class SCMViewPane extends ViewPane { this.inputRenderer, this.actionButtonRenderer, this.instantiationService.createInstance(RepositoryRenderer, MenuId.SCMTitle, getActionViewItemProvider(this.instantiationService)), - this.instantiationService.createInstance(ResourceGroupRenderer, getActionViewItemProvider(this.instantiationService)), + this.instantiationService.createInstance(ResourceGroupRenderer, getActionViewItemProvider(this.instantiationService), resourceActionRunner), this.instantiationService.createInstance(ResourceRenderer, () => this.viewMode, this.listLabels, getActionViewItemProvider(this.instantiationService), resourceActionRunner) ], treeDataSource, @@ -2611,9 +2617,8 @@ export class SCMViewPane extends ViewPane { return Array.from(new Set([...focusedRepositories, ...selectedRepositories])); } - private getSelectedResources(): (ISCMResource | IResourceNode)[] { - return this.tree.getSelection() - .filter(r => !!r && !isSCMResourceGroup(r))! as any; + private getSelectedResources(): (ISCMResourceGroup | ISCMResource | IResourceNode)[] { + return this.tree.getSelection().filter(r => isSCMResourceGroup(r) || isSCMResource(r) || isSCMResourceNode(r)); } private getViewMode(): ViewMode { From 5c4c37a9204b2b1ac111f5c0d550e23a9e0e0bb9 Mon Sep 17 00:00:00 2001 From: Christian Klaussner Date: Tue, 4 Feb 2025 23:20:03 +0100 Subject: [PATCH 0370/2632] Fix traffic light centering on macOS (#212471) Co-authored-by: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> --- src/vs/platform/windows/electron-main/windowImpl.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index 3c8efe30dd68..f5bf1b62cb9c 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -342,11 +342,16 @@ export abstract class BaseWindow extends Disposable implements IBaseWindow { // macOS: traffic lights else if (isMacintosh && options.height !== undefined) { - const verticalOffset = (options.height - 15) / 2; // 15px is the height of the traffic lights - if (!verticalOffset) { + // The traffic lights have a height of 12px. There's an invisible margin + // of 2px at the top and bottom, and 1px on the left and right. Therefore, + // the height for centering is 12px + 2 * 2px = 16px. When the position + // is set, the horizontal margin is offset to ensure the distance between + // the traffic lights and the window frame is equal in both directions. + const offset = Math.floor((options.height - 16) / 2); + if (!offset) { win.setWindowButtonPosition(null); } else { - win.setWindowButtonPosition({ x: verticalOffset, y: verticalOffset }); + win.setWindowButtonPosition({ x: offset + 1, y: offset }); } } } From c18871a3c0c7f4053198d51399829e9ff216b41b Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 4 Feb 2025 14:34:49 -0800 Subject: [PATCH 0371/2632] Re #239460. Empty notebook defaults cell language to last used kernel (#239647) --- .../notebook/browser/controller/cellOperations.ts | 14 +++++++++++++- .../browser/controller/insertCellActions.ts | 14 ++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts index 3432d899d1bf..0795f4536e70 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts @@ -18,6 +18,7 @@ import { CellEditType, CellKind, ICellEditOperation, ICellReplaceEdit, IOutputDt import { cellRangeContains, cellRangesToIndexes, ICellRange } from '../../common/notebookRange.js'; import { localize } from '../../../../../nls.js'; import { INotificationService } from '../../../../../platform/notification/common/notification.js'; +import { INotebookKernelHistoryService } from '../../common/notebookKernelService.js'; export async function changeCellToKind(kind: CellKind, context: INotebookActionContext, language?: string, mime?: string): Promise { const { notebookEditor } = context; @@ -662,7 +663,8 @@ export function insertCell( type: CellKind, direction: 'above' | 'below' = 'above', initialText: string = '', - ui: boolean = false + ui: boolean = false, + kernelHistoryService?: INotebookKernelHistoryService ) { const viewModel = editor.getViewModel() as NotebookViewModel; const activeKernel = editor.activeKernel; @@ -676,6 +678,7 @@ export function insertCell( if (type === CellKind.Code) { const supportedLanguages = activeKernel?.supportedLanguages ?? languageService.getRegisteredLanguageIds(); const defaultLanguage = supportedLanguages[0] || PLAINTEXT_LANGUAGE_ID; + if (cell?.cellKind === CellKind.Code) { language = cell.language; } else if (cell?.cellKind === CellKind.Markup) { @@ -685,6 +688,15 @@ export function insertCell( } else { language = defaultLanguage; } + } else if (!cell && viewModel.length === 0) { + // No cells in notebook - check kernel history + const lastKernels = kernelHistoryService?.getKernels(viewModel.notebookDocument); + if (lastKernels?.all.length) { + const lastKernel = lastKernels.all[0]; + language = lastKernel.supportedLanguages[0] || defaultLanguage; + } else { + language = defaultLanguage; + } } else { if (cell === undefined && direction === 'above') { // insert cell at the very top diff --git a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts index dc3fbcc179db..b7bdf36f3478 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts @@ -18,6 +18,7 @@ import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_EDITABLE } from '../../comm import { CellViewModel } from '../viewModel/notebookViewModelImpl.js'; import { CellKind, NotebookSetting } from '../../common/notebookCommon.js'; import { CTX_NOTEBOOK_CHAT_OUTER_FOCUS_POSITION } from './chat/notebookChatContext.js'; +import { INotebookKernelHistoryService } from '../../common/notebookKernelService.js'; const INSERT_CODE_CELL_ABOVE_COMMAND_ID = 'notebook.cell.insertCodeCellAbove'; const INSERT_CODE_CELL_BELOW_COMMAND_ID = 'notebook.cell.insertCodeCellBelow'; @@ -35,13 +36,15 @@ export function insertNewCell(accessor: ServicesAccessor, context: INotebookActi } const languageService = accessor.get(ILanguageService); + const kernelHistoryService = accessor.get(INotebookKernelHistoryService); + if (context.cell) { const idx = context.notebookEditor.getCellIndex(context.cell); - newCell = insertCell(languageService, context.notebookEditor, idx, kind, direction, undefined, true); + newCell = insertCell(languageService, context.notebookEditor, idx, kind, direction, undefined, true, kernelHistoryService); } else { const focusRange = context.notebookEditor.getFocus(); const next = Math.max(focusRange.end - 1, 0); - newCell = insertCell(languageService, context.notebookEditor, next, kind, direction, undefined, true); + newCell = insertCell(languageService, context.notebookEditor, next, kind, direction, undefined, true, kernelHistoryService); } return newCell; @@ -193,7 +196,8 @@ registerAction2(class InsertCodeCellAtTopAction extends NotebookAction { async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { const languageService = accessor.get(ILanguageService); - const newCell = insertCell(languageService, context.notebookEditor, 0, CellKind.Code, 'above', undefined, true); + const kernelHistoryService = accessor.get(INotebookKernelHistoryService); + const newCell = insertCell(languageService, context.notebookEditor, 0, CellKind.Code, 'above', undefined, true, kernelHistoryService); if (newCell) { await context.notebookEditor.focusNotebookCell(newCell, 'editor'); @@ -220,7 +224,9 @@ registerAction2(class InsertMarkdownCellAtTopAction extends NotebookAction { async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { const languageService = accessor.get(ILanguageService); - const newCell = insertCell(languageService, context.notebookEditor, 0, CellKind.Markup, 'above', undefined, true); + const kernelHistoryService = accessor.get(INotebookKernelHistoryService); + + const newCell = insertCell(languageService, context.notebookEditor, 0, CellKind.Markup, 'above', undefined, true, kernelHistoryService); if (newCell) { await context.notebookEditor.focusNotebookCell(newCell, 'editor'); From 363d424f91f0e47e871c9b8e21ec6bb2afb7a2db Mon Sep 17 00:00:00 2001 From: Justin Chen <54879025+justschen@users.noreply.github.com> Date: Tue, 4 Feb 2025 14:37:31 -0800 Subject: [PATCH 0372/2632] adopt `ensureNoDisposablesAreLeakedInTestSuite` (#239536) * adopt * fix import change * remove disposablestore and organize imports * fix disposable leak error * more disposables fix * rename to code actions disposable --------- Co-authored-by: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> --- eslint.config.js | 1 - .../contrib/codeAction/browser/codeAction.ts | 19 +++++++----- .../codeAction/browser/codeActionModel.ts | 28 +++++++++++------ .../test/browser/codeActionModel.test.ts | 31 +++++++++---------- 4 files changed, 44 insertions(+), 35 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index f9f120acd41b..b3167b40a5c7 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -219,7 +219,6 @@ export default tseslint.config( { // Files should (only) be removed from the list they adopt the leak detector 'exclude': [ - 'src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts', 'src/vs/platform/configuration/test/common/configuration.test.ts', 'src/vs/platform/opener/test/common/opener.test.ts', 'src/vs/platform/registry/test/common/platform.test.ts', diff --git a/src/vs/editor/contrib/codeAction/browser/codeAction.ts b/src/vs/editor/contrib/codeAction/browser/codeAction.ts index 6120bae3584a..c78ecc080d4c 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeAction.ts @@ -6,8 +6,16 @@ import { coalesce, equals, isNonEmptyArray } from '../../../../base/common/arrays.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { illegalArgument, isCancellationError, onUnexpectedExternalError } from '../../../../base/common/errors.js'; +import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; +import * as nls from '../../../../nls.js'; +import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; +import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js'; +import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { IProgress, Progress } from '../../../../platform/progress/common/progress.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { IBulkEditService } from '../../../browser/services/bulkEditService.js'; import { Range } from '../../../common/core/range.js'; @@ -18,15 +26,7 @@ import { ITextModel } from '../../../common/model.js'; import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js'; import { IModelService } from '../../../common/services/model.js'; import { TextModelCancellationTokenSource } from '../../editorState/browser/editorState.js'; -import * as nls from '../../../../nls.js'; -import { CommandsRegistry, ICommandService } from '../../../../platform/commands/common/commands.js'; -import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; -import { INotificationService } from '../../../../platform/notification/common/notification.js'; -import { IProgress, Progress } from '../../../../platform/progress/common/progress.js'; -import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { CodeActionFilter, CodeActionItem, CodeActionKind, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource, filtersAction, mayIncludeActionsOfKind } from '../common/types.js'; -import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; -import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; export const codeActionCommandId = 'editor.action.codeAction'; export const quickFixCommandId = 'editor.action.quickFix'; @@ -165,6 +165,9 @@ export async function getCodeActions( ...getAdditionalDocumentationForShowingActions(registry, model, trigger, allActions) ]; return new ManagedCodeActionSet(allActions, allDocumentation, disposables); + } catch (err) { + disposables.dispose(); + throw err; } finally { listener.dispose(); cts.dispose(); diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts b/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts index 10381ff4ffce..d5289c2cb6bc 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts @@ -6,24 +6,24 @@ import { CancelablePromise, createCancelablePromise, TimeoutTimer } from '../../../../base/common/async.js'; import { isCancellationError } from '../../../../base/common/errors.js'; import { Emitter } from '../../../../base/common/event.js'; -import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; +import { Disposable, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { isEqual } from '../../../../base/common/resources.js'; +import { StopWatch } from '../../../../base/common/stopwatch.js'; import { URI } from '../../../../base/common/uri.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IMarkerService } from '../../../../platform/markers/common/markers.js'; +import { IEditorProgressService, Progress } from '../../../../platform/progress/common/progress.js'; +import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { ICodeEditor } from '../../../browser/editorBrowser.js'; import { EditorOption, ShowLightbulbIconMode } from '../../../common/config/editorOptions.js'; import { Position } from '../../../common/core/position.js'; import { Selection } from '../../../common/core/selection.js'; import { LanguageFeatureRegistry } from '../../../common/languageFeatureRegistry.js'; import { CodeActionProvider, CodeActionTriggerType } from '../../../common/languages.js'; -import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; -import { IMarkerService } from '../../../../platform/markers/common/markers.js'; -import { IEditorProgressService, Progress } from '../../../../platform/progress/common/progress.js'; import { CodeActionKind, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from '../common/types.js'; import { getCodeActions } from './codeAction.js'; -import { HierarchicalKind } from '../../../../base/common/hierarchicalKind.js'; -import { StopWatch } from '../../../../base/common/stopwatch.js'; -import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; export const SUPPORTED_CODE_ACTIONS = new RawContextKey('supportedCodeAction', ''); @@ -165,6 +165,8 @@ export class CodeActionModel extends Disposable { private readonly _onDidChangeState = this._register(new Emitter()); public readonly onDidChangeState = this._onDidChangeState.event; + private readonly codeActionsDisposable: MutableDisposable = this._register(new MutableDisposable()); + private _disposed = false; constructor( @@ -233,6 +235,7 @@ export class CodeActionModel extends Disposable { const actions = createCancelablePromise(async token => { if (this._settingEnabledNearbyQuickfixes() && trigger.trigger.type === CodeActionTriggerType.Invoke && (trigger.trigger.triggerAction === CodeActionTriggerSource.QuickFix || trigger.trigger.filter?.include?.contains(CodeActionKind.QuickFix))) { const codeActionSet = await getCodeActions(this._registry, model, trigger.selection, trigger.trigger, Progress.None, token); + this.codeActionsDisposable.value = codeActionSet; const allCodeActions = [...codeActionSet.allActions]; if (token.isCancellationRequested) { codeActionSet.dispose(); @@ -250,7 +253,7 @@ export class CodeActionModel extends Disposable { } return { validActions: codeActionSet.validActions, allActions: allCodeActions, documentation: codeActionSet.documentation, hasAutoFix: codeActionSet.hasAutoFix, hasAIFix: codeActionSet.hasAIFix, allAIFixes: codeActionSet.allAIFixes, dispose: () => { codeActionSet.dispose(); } }; } else if (!foundQuickfix) { - // If markers exists, and there are no quickfixes found or length is zero, check for quickfixes on that line. + // If markers exist, and there are no quickfixes found or length is zero, check for quickfixes on that line. if (allMarkers.length > 0) { const currPosition = trigger.selection.getPosition(); let trackedPosition = currPosition; @@ -275,6 +278,7 @@ export class CodeActionModel extends Disposable { const selectionAsPosition = new Selection(trackedPosition.lineNumber, trackedPosition.column, trackedPosition.lineNumber, trackedPosition.column); const actionsAtMarker = await getCodeActions(this._registry, model, selectionAsPosition, newCodeActionTrigger, Progress.None, token); + this.codeActionsDisposable.value = actionsAtMarker; if (actionsAtMarker.validActions.length !== 0) { for (const action of actionsAtMarker.validActions) { @@ -348,8 +352,11 @@ export class CodeActionModel extends Disposable { return codeActions; } - return getCodeActions(this._registry, model, trigger.selection, trigger.trigger, Progress.None, token); + const codeActionSet = await getCodeActions(this._registry, model, trigger.selection, trigger.trigger, Progress.None, token); + this.codeActionsDisposable.value = codeActionSet; + return codeActionSet; }); + if (trigger.trigger.type === CodeActionTriggerType.Invoke) { this._progressService?.showWhile(actions, 250); } @@ -381,6 +388,7 @@ export class CodeActionModel extends Disposable { public trigger(trigger: CodeActionTrigger) { this._codeActionOracle.value?.trigger(trigger); + this.codeActionsDisposable.clear(); } private setState(newState: CodeActionsState.State, skipNotify?: boolean) { diff --git a/src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts b/src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts index 666b185bda57..ab6c174582a9 100644 --- a/src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts +++ b/src/vs/editor/contrib/codeAction/test/browser/codeActionModel.test.ts @@ -5,19 +5,19 @@ import assert from 'assert'; import { promiseWithResolvers } from '../../../../../base/common/async.js'; -import { DisposableStore } from '../../../../../base/common/lifecycle.js'; import { assertType } from '../../../../../base/common/types.js'; import { URI } from '../../../../../base/common/uri.js'; import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js'; +import { MarkerService } from '../../../../../platform/markers/common/markerService.js'; import { ICodeEditor } from '../../../../browser/editorBrowser.js'; import { LanguageFeatureRegistry } from '../../../../common/languageFeatureRegistry.js'; import * as languages from '../../../../common/languages.js'; import { TextModel } from '../../../../common/model/textModel.js'; -import { CodeActionModel, CodeActionsState } from '../../browser/codeActionModel.js'; import { createTestCodeEditor } from '../../../../test/browser/testCodeEditor.js'; import { createTextModel } from '../../../../test/common/testTextModel.js'; -import { MockContextKeyService } from '../../../../../platform/keybinding/test/common/mockKeybindingService.js'; -import { MarkerService } from '../../../../../platform/markers/common/markerService.js'; +import { CodeActionModel, CodeActionsState } from '../../browser/codeActionModel.js'; const testProvider = { provideCodeActions(): languages.CodeActionList { @@ -38,10 +38,8 @@ suite('CodeActionModel', () => { let markerService: MarkerService; let editor: ICodeEditor; let registry: LanguageFeatureRegistry; - const disposables = new DisposableStore(); setup(() => { - disposables.clear(); markerService = new MarkerService(); model = createTextModel('foobar foo bar\nfarboo far boo', languageId, undefined, uri); editor = createTestCodeEditor(model); @@ -49,8 +47,9 @@ suite('CodeActionModel', () => { registry = new LanguageFeatureRegistry(); }); + const store = ensureNoDisposablesAreLeakedInTestSuite(); + teardown(() => { - disposables.clear(); editor.dispose(); model.dispose(); markerService.dispose(); @@ -61,11 +60,11 @@ suite('CodeActionModel', () => { await runWithFakedTimers({ useFakeTimers: true }, () => { const reg = registry.register(languageId, testProvider); - disposables.add(reg); + store.add(reg); const contextKeys = new MockContextKeyService(); - const model = disposables.add(new CodeActionModel(editor, registry, markerService, contextKeys, undefined)); - disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { + const model = store.add(new CodeActionModel(editor, registry, markerService, contextKeys, undefined)); + store.add(model.onDidChangeState((e: CodeActionsState.State) => { assertType(e.type === CodeActionsState.Type.Triggered); assert.strictEqual(e.trigger.type, languages.CodeActionTriggerType.Auto); @@ -93,7 +92,7 @@ suite('CodeActionModel', () => { test('Oracle -> position changed', async () => { await runWithFakedTimers({ useFakeTimers: true }, () => { const reg = registry.register(languageId, testProvider); - disposables.add(reg); + store.add(reg); markerService.changeOne('fake', uri, [{ startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6, @@ -107,8 +106,8 @@ suite('CodeActionModel', () => { return new Promise((resolve, reject) => { const contextKeys = new MockContextKeyService(); - const model = disposables.add(new CodeActionModel(editor, registry, markerService, contextKeys, undefined)); - disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { + const model = store.add(new CodeActionModel(editor, registry, markerService, contextKeys, undefined)); + store.add(model.onDidChangeState((e: CodeActionsState.State) => { assertType(e.type === CodeActionsState.Type.Triggered); assert.strictEqual(e.trigger.type, languages.CodeActionTriggerType.Auto); @@ -129,12 +128,12 @@ suite('CodeActionModel', () => { const { promise: donePromise, resolve: done } = promiseWithResolvers(); await runWithFakedTimers({ useFakeTimers: true }, () => { const reg = registry.register(languageId, testProvider); - disposables.add(reg); + store.add(reg); let triggerCount = 0; const contextKeys = new MockContextKeyService(); - const model = disposables.add(new CodeActionModel(editor, registry, markerService, contextKeys, undefined)); - disposables.add(model.onDidChangeState((e: CodeActionsState.State) => { + const model = store.add(new CodeActionModel(editor, registry, markerService, contextKeys, undefined)); + store.add(model.onDidChangeState((e: CodeActionsState.State) => { assertType(e.type === CodeActionsState.Type.Triggered); assert.strictEqual(e.trigger.type, languages.CodeActionTriggerType.Auto); From 1195e2cf772e4f9b3bfeab7ba20345bb68deff61 Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Tue, 4 Feb 2025 15:35:23 -0800 Subject: [PATCH 0373/2632] fix the `the the` typos (#239646) fix the `the the` -> `the` typos --- .devcontainer/README.md | 2 +- extensions/ipynb/src/notebookModelStoreSync.ts | 2 +- .../test/colorize-fixtures/test-treeView.ts | 4 ++-- src/vs/base/browser/ui/hover/hover.ts | 2 +- src/vs/editor/browser/editorDom.ts | 2 +- src/vs/editor/browser/gpu/atlas/atlas.ts | 4 ++-- src/vs/editor/browser/gpu/raster/raster.ts | 4 ++-- src/vs/editor/browser/view/viewLayer.ts | 2 +- src/vs/editor/common/textModelGuides.ts | 2 +- .../extensionManagement/common/extensionGalleryService.ts | 4 ++-- .../common/capabilities/commandDetectionCapability.ts | 2 +- src/vs/workbench/browser/parts/views/treeView.ts | 4 ++-- src/vs/workbench/contrib/chat/browser/media/chat.css | 2 +- src/vs/workbench/contrib/comments/browser/commentService.ts | 2 +- src/vs/workbench/contrib/comments/browser/commentsModel.ts | 2 +- .../externalUriOpener/common/externalUriOpenerService.ts | 2 +- .../contrib/markdown/browser/markdownSettingRenderer.ts | 2 +- src/vs/workbench/contrib/terminal/browser/terminal.ts | 2 +- src/vs/workbench/contrib/terminal/browser/terminalActions.ts | 2 +- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 2 +- .../contrib/workspace/browser/workspaceTrustEditor.ts | 2 +- src/vscode-dts/vscode.d.ts | 2 +- src/vscode-dts/vscode.proposed.resolvers.d.ts | 2 +- src/vscode-dts/vscode.proposed.tunnelFactory.d.ts | 2 +- test/automation/src/terminal.ts | 2 +- 25 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 809b0f6aa557..3d1e15e5d550 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -56,7 +56,7 @@ Next: **[Try it out!](#try-it)** You may see improved VNC responsiveness when accessing a codespace from VS Code client since you can use a [VNC Viewer][def]. Here's how to do it. -1. Install [Visual Studio Code Stable](https://code.visualstudio.com/) or [Insiders](https://code.visualstudio.com/insiders/) and the the [GitHub Codespaces extension](https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces). +1. Install [Visual Studio Code Stable](https://code.visualstudio.com/) or [Insiders](https://code.visualstudio.com/insiders/) and the [GitHub Codespaces extension](https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces). > **Note:** The GitHub Codespaces extension requires the Visual Studio Code distribution of Code - OSS. diff --git a/extensions/ipynb/src/notebookModelStoreSync.ts b/extensions/ipynb/src/notebookModelStoreSync.ts index 836e1c8afc5d..1d83d980b2da 100644 --- a/extensions/ipynb/src/notebookModelStoreSync.ts +++ b/extensions/ipynb/src/notebookModelStoreSync.ts @@ -14,7 +14,7 @@ const noop = () => { }; /** - * Code here is used to ensure the Notebook Model is in sync the the ipynb JSON file. + * Code here is used to ensure the Notebook Model is in sync the ipynb JSON file. * E.g. assume you add a new cell, this new cell will not have any metadata at all. * However when we save the ipynb, the metadata will be an empty object `{}`. * Now thats completely different from the metadata os being `empty/undefined` in the model. diff --git a/extensions/vscode-colorize-perf-tests/test/colorize-fixtures/test-treeView.ts b/extensions/vscode-colorize-perf-tests/test/colorize-fixtures/test-treeView.ts index 08927639e762..612ee41bc2af 100644 --- a/extensions/vscode-colorize-perf-tests/test/colorize-fixtures/test-treeView.ts +++ b/extensions/vscode-colorize-perf-tests/test/colorize-fixtures/test-treeView.ts @@ -300,7 +300,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { } this._isInitialized = true; - // Remember when adding to this method that it isn't called until the the view is visible, meaning that + // Remember when adding to this method that it isn't called until the view is visible, meaning that // properties could be set and events could be fired before we're initialized and that this needs to be handled. this.contextKeyService.bufferChangeEvents(() => { @@ -534,7 +534,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { private initializeShowCollapseAllAction(startingValue: boolean = false) { if (!this.collapseAllContext) { - this.collapseAllContextKey = new RawContextKey(`treeView.${this.id}.enableCollapseAll`, startingValue, localize('treeView.enableCollapseAll', "Whether the the tree view with id {0} enables collapse all.", this.id)); + this.collapseAllContextKey = new RawContextKey(`treeView.${this.id}.enableCollapseAll`, startingValue, localize('treeView.enableCollapseAll', "Whether the tree view with id {0} enables collapse all.", this.id)); this.collapseAllContext = this.collapseAllContextKey.bindTo(this.contextKeyService); } return true; diff --git a/src/vs/base/browser/ui/hover/hover.ts b/src/vs/base/browser/ui/hover/hover.ts index 7f30cd1854e6..ec7408607080 100644 --- a/src/vs/base/browser/ui/hover/hover.ts +++ b/src/vs/base/browser/ui/hover/hover.ts @@ -101,7 +101,7 @@ export interface IHoverDelegate2 { ): IDisposable; /** - * Hides the hover if it was visible. This call will be ignored if the the hover is currently + * Hides the hover if it was visible. This call will be ignored if the hover is currently * "locked" via the alt/option key. */ hideHover(): void; diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts index e42959d81b6f..5a3627d87913 100644 --- a/src/vs/editor/browser/editorDom.ts +++ b/src/vs/editor/browser/editorDom.ts @@ -64,7 +64,7 @@ export class EditorPagePosition { } /** - * Coordinates relative to the the (top;left) of the editor that can be used safely with other internal editor metrics. + * Coordinates relative to the (top;left) of the editor that can be used safely with other internal editor metrics. * **NOTE**: This position is obtained by taking page coordinates and transforming them relative to the * editor's (top;left) position in a way in which scale transformations are taken into account. * **NOTE**: These coordinates could be negative if the mouse position is outside the editor. diff --git a/src/vs/editor/browser/gpu/atlas/atlas.ts b/src/vs/editor/browser/gpu/atlas/atlas.ts index 17900d5273c8..7ef9da7d5f55 100644 --- a/src/vs/editor/browser/gpu/atlas/atlas.ts +++ b/src/vs/editor/browser/gpu/atlas/atlas.ts @@ -32,14 +32,14 @@ export interface ITextureAtlasPageGlyph { /** The y offset from {@link y} of the glyph's origin. */ originOffsetY: number; /** - * The distance from the the glyph baseline to the top of the highest bounding rectangle of all + * The distance from the glyph baseline to the top of the highest bounding rectangle of all * fonts used to render the text. * * @see {@link TextMetrics.fontBoundingBoxAscent} */ fontBoundingBoxAscent: number; /** - * The distance from the the glyph baseline to the bottom of the bounding rectangle of all fonts + * The distance from the glyph baseline to the bottom of the bounding rectangle of all fonts * used to render the text. * * @see {@link TextMetrics.fontBoundingBoxDescent} diff --git a/src/vs/editor/browser/gpu/raster/raster.ts b/src/vs/editor/browser/gpu/raster/raster.ts index eb6c56f1ffd3..32bee48f24d8 100644 --- a/src/vs/editor/browser/gpu/raster/raster.ts +++ b/src/vs/editor/browser/gpu/raster/raster.ts @@ -67,14 +67,14 @@ export interface IRasterizedGlyph { */ originOffset: { x: number; y: number }; /** - * The distance from the the glyph baseline to the top of the highest bounding rectangle of all + * The distance from the glyph baseline to the top of the highest bounding rectangle of all * fonts used to render the text. * * @see {@link TextMetrics.fontBoundingBoxAscent} */ fontBoundingBoxAscent: number; /** - * The distance from the the glyph baseline to the bottom of the bounding rectangle of all fonts + * The distance from the glyph baseline to the bottom of the bounding rectangle of all fonts * used to render the text. * * @see {@link TextMetrics.fontBoundingBoxDescent} diff --git a/src/vs/editor/browser/view/viewLayer.ts b/src/vs/editor/browser/view/viewLayer.ts index ec5d8bce6f3d..4058205be2fe 100644 --- a/src/vs/editor/browser/view/viewLayer.ts +++ b/src/vs/editor/browser/view/viewLayer.ts @@ -279,7 +279,7 @@ export class VisibleLinesCollection { public onFlushed(e: viewEvents.ViewFlushedEvent, flushDom?: boolean): boolean { // No need to clear the dom node because a full .innerHTML will occur in - // ViewLayerRenderer._render, however the the fallbakc mechanism in the + // ViewLayerRenderer._render, however the fallback mechanism in the // GPU renderer may cause this to be necessary as the .innerHTML call // may not happen depending on the new state, leaving stale DOM nodes // around. diff --git a/src/vs/editor/common/textModelGuides.ts b/src/vs/editor/common/textModelGuides.ts index a109109e430e..f1cd766ec3f8 100644 --- a/src/vs/editor/common/textModelGuides.ts +++ b/src/vs/editor/common/textModelGuides.ts @@ -17,7 +17,7 @@ export interface IGuidesTextModelPart { getLinesIndentGuides(startLineNumber: number, endLineNumber: number): number[]; /** - * Requests the the indent guides for the given range of lines. + * Requests the indent guides for the given range of lines. * `result[i]` will contain the indent guides of the `startLineNumber + i`th line. * @internal */ diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index 90b110c251ae..83b0491704bf 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -688,7 +688,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi { count: number }, { owner: 'sandy081'; - comment: 'Report the query to the the Marketplace for fetching extensions by name'; + comment: 'Report the query to the Marketplace for fetching extensions by name'; readonly count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions to fetch' }; }>('galleryService:additionalQueryByName', { count: extensionInfosByName.length @@ -1296,7 +1296,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi finally { type GalleryServiceGetLatestEventClassification = { owner: 'sandy081'; - comment: 'Report the query to the the Marketplace for fetching latest version of an extension'; + comment: 'Report the query to the Marketplace for fetching latest version of an extension'; extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the extension' }; duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Duration in ms for the query' }; errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' }; diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 8f9d90cf49b7..d2413b5897ea 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -358,7 +358,7 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe this._logService.debug('CommandDetectionCapability#handleCommandFinished', this._terminal.buffer.active.cursorX, options?.marker?.line, this._currentCommand.command, this._currentCommand); // HACK: Handle a special case on some versions of bash where identical commands get merged - // in the output of `history`, this detects that case and sets the exit code to the the last + // in the output of `history`, this detects that case and sets the exit code to the last // command's exit code. This covered the majority of cases but will fail if the same command // runs with a different exit code, that will need a more robust fix where we send the // command ID and exit code over to the capability to adjust there. diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 3384ee711fa1..84027d9d013c 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -300,7 +300,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { } this._isInitialized = true; - // Remember when adding to this method that it isn't called until the the view is visible, meaning that + // Remember when adding to this method that it isn't called until the view is visible, meaning that // properties could be set and events could be fired before we're initialized and that this needs to be handled. this.contextKeyService.bufferChangeEvents(() => { @@ -534,7 +534,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { private initializeShowCollapseAllAction(startingValue: boolean = false) { if (!this.collapseAllContext) { - this.collapseAllContextKey = new RawContextKey(`treeView.${this.id}.enableCollapseAll`, startingValue, localize('treeView.enableCollapseAll', "Whether the the tree view with id {0} enables collapse all.", this.id)); + this.collapseAllContextKey = new RawContextKey(`treeView.${this.id}.enableCollapseAll`, startingValue, localize('treeView.enableCollapseAll', "Whether the tree view with id {0} enables collapse all.", this.id)); this.collapseAllContext = this.collapseAllContextKey.bindTo(this.contextKeyService); } return true; diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 9a0c57c3bc56..138dd32b1507 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -996,7 +996,7 @@ have to be updated for changes to the rules above, or to support more deeply nes } /* * This overly-specific CSS selector is needed to beat priority of some - * styles applied on the the `.chat-attached-context-attachment` element. + * styles applied on the `.chat-attached-context-attachment` element. */ .chat-attached-context .chat-prompt-instructions-attachments .chat-prompt-instructions-attachment.error.implicit, .chat-attached-context .chat-prompt-instructions-attachments .chat-prompt-instructions-attachment.warning.implicit { diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index b7d1cde9aead..309745ca99d2 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -293,7 +293,7 @@ export class CommentService extends Disposable implements ICommentService { } /** - * The active comment thread is the the thread that is currently being edited. + * The active comment thread is the thread that is currently being edited. * @param commentThread */ setActiveEditingCommentThread(commentThread: CommentThread | null) { diff --git a/src/vs/workbench/contrib/comments/browser/commentsModel.ts b/src/vs/workbench/contrib/comments/browser/commentsModel.ts index 909955ccd20d..a2191238f6e8 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsModel.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsModel.ts @@ -121,7 +121,7 @@ export class CommentsModel extends Disposable implements ICommentsModel { public hasCommentThreads(): boolean { // There's a resource with at least one thread return !!this._resourceCommentThreads.length && this._resourceCommentThreads.some(resource => { - // At least one of the threads in the the resource has comments + // At least one of the threads in the resource has comments return (resource.commentThreads.length > 0) && resource.commentThreads.some(thread => { // At least one of the comments in the thread is not empty return threadHasMeaningfulComments(thread.thread); diff --git a/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts b/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts index fe02f19396d1..68495c2158db 100644 --- a/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts +++ b/src/vs/workbench/contrib/externalUriOpener/common/externalUriOpenerService.ts @@ -45,7 +45,7 @@ export interface IExternalUriOpenerService { registerExternalOpenerProvider(provider: IExternalOpenerProvider): IDisposable; /** - * Get the configured IExternalUriOpener for the the uri. + * Get the configured IExternalUriOpener for the uri. * If there is no opener configured, then returns the first opener that can handle the uri. */ getOpener(uri: URI, ctx: { sourceUri: URI; preferredOpenerId?: string }, token: CancellationToken): Promise; diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 14dfabbd451c..5cf78c4d4759 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -299,7 +299,7 @@ export class SimpleSettingRenderer { if (uri.scheme === Schemas.codeSetting) { type ReleaseNotesSettingUsedClassification = { owner: 'alexr00'; - comment: 'Used to understand if the the action to update settings from the release notes is used.'; + comment: 'Used to understand if the action to update settings from the release notes is used.'; settingId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The id of the setting that was clicked on in the release notes' }; }; type ReleaseNotesSettingUsed = { diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 7e65338102bc..d5d2b44a3a8c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -973,7 +973,7 @@ export interface ITerminalInstance extends IBaseTerminalInstance { /** * Sets the terminal instance's dimensions to the values provided via the onDidOverrideDimensions event, - * which allows overriding the the regular dimensions (fit to the size of the panel). + * which allows overriding the regular dimensions (fit to the size of the panel). */ setOverrideDimensions(dimensions: ITerminalDimensions): void; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 00927f236f3c..664eea289081 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -204,7 +204,7 @@ export function registerContextualInstanceAction( activeInstanceType?: 'view' | 'editor'; run: (instance: ITerminalInstance, c: ITerminalServicesCollection, accessor: ServicesAccessor, args?: unknown) => void | Promise; /** - * A callback to run after the the `run` callbacks have completed. + * A callback to run after the `run` callbacks have completed. * @param instances The selected instance(s) that the command was run on. */ runAfter?: (instances: ITerminalInstance[], c: ITerminalServicesCollection, accessor: ServicesAccessor, args?: unknown) => void | Promise; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 35f316a4bd0c..bffdd6596871 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -951,7 +951,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } /** - * Opens the the terminal instance inside the parent DOM element previously set with + * Opens the terminal instance inside the parent DOM element previously set with * `attachToElement`, you must ensure the parent DOM element is explicitly visible before * invoking this function as it performs some DOM calculations internally */ diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts index 8d37002068d8..fb904f26b045 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts @@ -1091,7 +1091,7 @@ export class WorkspaceTrustEditor extends EditorPane { const textElement = append(parent, $('.workspace-trust-untrusted-description')); if (!this.workspaceTrustManagementService.isWorkspaceTrustForced()) { - textElement.innerText = this.workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE ? localize('untrustedWorkspaceReason', "This workspace is trusted via the bolded entries in the trusted folders below.") : localize('untrustedFolderReason', "This folder is trusted via the bolded entries in the the trusted folders below."); + textElement.innerText = this.workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE ? localize('untrustedWorkspaceReason', "This workspace is trusted via the bolded entries in the trusted folders below.") : localize('untrustedFolderReason', "This folder is trusted via the bolded entries in the trusted folders below."); } else { textElement.innerText = localize('trustedForcedReason', "This window is trusted by nature of the workspace that is opened."); } diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 67d44daf01f1..55ea480be702 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -11906,7 +11906,7 @@ declare module 'vscode' { * A map containing a mapping of the mime type of the corresponding transferred data. * * Drag and drop controllers that implement {@link TreeDragAndDropController.handleDrag `handleDrag`} can add additional mime types to the - * data transfer. These additional mime types will only be included in the `handleDrop` when the the drag was initiated from + * data transfer. These additional mime types will only be included in the `handleDrop` when the drag was initiated from * an element in the same drag and drop controller. */ export class DataTransfer implements Iterable<[mimeType: string, item: DataTransferItem]> { diff --git a/src/vscode-dts/vscode.proposed.resolvers.d.ts b/src/vscode-dts/vscode.proposed.resolvers.d.ts index 68a2c0639279..5df4f6fc1c52 100644 --- a/src/vscode-dts/vscode.proposed.resolvers.d.ts +++ b/src/vscode-dts/vscode.proposed.resolvers.d.ts @@ -121,7 +121,7 @@ declare module 'vscode' { tunnelFeatures?: { elevation: boolean; /** - * One of the the options must have the ID "private". + * One of the options must have the ID "private". */ privacyOptions: TunnelPrivacy[]; /** diff --git a/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts b/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts index 828a948b80f6..eab3ffa5d2e4 100644 --- a/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts +++ b/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts @@ -19,7 +19,7 @@ declare module 'vscode' { tunnelFeatures?: { elevation: boolean; /** - * One of the the options must have the ID "private". + * One of the options must have the ID "private". */ privacyOptions: TunnelPrivacy[]; /** diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index 8ebcbd686361..1b3ecc7cdfaa 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -210,7 +210,7 @@ export class Terminal { name: title.textContent.replace(/^[├┌└]\s*/, ''), description: description?.textContent }; - // It's a new group if the the tab does not start with ├ or └ + // It's a new group if the tab does not start with ├ or └ if (title.textContent.match(/^[├└]/)) { groups[groups.length - 1].push(label); } else { From 194a08f5f2572012863f15a2c9e7c71e990cb92e Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Tue, 4 Feb 2025 17:32:27 -0800 Subject: [PATCH 0374/2632] [prompts]: implement basic paths autocompletion for `Unix` machines (#239643) * [autocompletions]: II for `#file:` paths * [autocompletions]: refactor and add more doc comments * [autocompletions]: refactor the provider to use the new syntax service * [autocompletions]: refactor, add unit tests --- src/vs/base/common/types.ts | 38 ++ src/vs/base/test/common/types.test.ts | 552 +++++++++++++++++- .../contrib/chat/browser/chat.contribution.ts | 1 + .../languageFeatures/promptLinkProvider.ts | 2 +- .../promptPathAutocompletion.ts | 362 ++++++++++++ .../common/promptSyntax/parsers/types.d.ts | 10 + 6 files changed, 963 insertions(+), 2 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptPathAutocompletion.ts diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 54edfafe71fe..b1e0b54f779d 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -163,6 +163,44 @@ export function assertAllDefined(...args: (unknown | null | undefined)[]): unkno return result; } +/** + * Asserts that the provided `item` is one of the items in the `list`. + * Helps to narrow down broader `TType` of the `item` to the more + * specific `TSubtype` type. + * + * ## Examples + * + * ```typescript + * // note! item type is a `subset of string` + * type TItem = ':' | '.' | '/'; + * + * // note! item is type of `string` here + * const item: string = ':'; + * // list of the items to check against + * const list: TItem[] = [':', '.']; + * + * // ok + * assertOneOf( + * item, + * list, + * 'Must succeed', + * ); + * + * // `item` is of `TItem` type now + * ``` + */ +export function assertOneOf( + item: TType, + list: readonly TSubtype[], + errorPrefix: string, +): asserts item is TSubtype { + // note! it's ok to type cast here because `TSubtype` is a subtype of `TType` + assert( + list.includes(item as TSubtype), + `${errorPrefix}: Expected '${item}' to be one of [${list.join(', ')}].`, + ); +} + const hasOwnProperty = Object.prototype.hasOwnProperty; /** diff --git a/src/vs/base/test/common/types.test.ts b/src/vs/base/test/common/types.test.ts index da0055392010..163b8d576855 100644 --- a/src/vs/base/test/common/types.test.ts +++ b/src/vs/base/test/common/types.test.ts @@ -5,8 +5,8 @@ import assert from 'assert'; import * as types from '../../common/types.js'; +import { assertDefined, assertOneOf } from '../../common/types.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from './utils.js'; -import { assertDefined } from '../../common/types.js'; suite('Types', () => { @@ -304,6 +304,556 @@ suite('Types', () => { }); }); + suite('assertOneOf', () => { + suite('success', () => { + /** + * Simple type check function to compile-time validate + * type of passed argument. + */ + function typeCheck(thing: T): T { + return thing; + } + + suite('string', () => { + test('type', () => { + assert.doesNotThrow(() => { + assertOneOf( + 'foo', + ['foo', 'bar'], + 'Foo must be one of: foo, bar', + ); + }); + }); + + test('subtype', () => { + assert.doesNotThrow(() => { + const item: string = 'hi'; + const list: ('hi' | 'ciao' | 'hola')[] = ['hi', 'ciao']; + + assertOneOf( + item, + list, + 'Hi must be one of: hi, ciao', + ); + + typeCheck<'hi' | 'ciao' | 'hola'>(item); + }); + }); + }); + + suite('number', () => { + test('type', () => { + assert.doesNotThrow(() => { + assertOneOf( + 10, + [10, 100], + '10 must be one of: 10, 100', + ); + }); + }); + + test('subtype', () => { + assert.doesNotThrow(() => { + const item: number = 20; + const list: (20 | 2000)[] = [20, 2000]; + + assertOneOf( + item, + list, + '20 must be one of: 20, 2000', + ); + + typeCheck<20 | 2000>(item); + }); + }); + + }); + + suite('boolean', () => { + test('type', () => { + assert.doesNotThrow(() => { + assertOneOf( + true, + [true, false], + 'true must be one of: true, false', + ); + }); + + assert.doesNotThrow(() => { + assertOneOf( + false, + [true, false], + 'false must be one of: true, false', + ); + }); + }); + + test('subtype (true)', () => { + assert.doesNotThrow(() => { + const item: boolean = true; + const list: (true)[] = [true, true]; + + assertOneOf( + item, + list, + 'true must be one of: true, true', + ); + + typeCheck(item); + }); + }); + + test('subtype (false)', () => { + assert.doesNotThrow(() => { + const item: boolean = false; + const list: (false | true)[] = [false, true]; + + assertOneOf( + item, + list, + 'false must be one of: false, true', + ); + + typeCheck(item); + }); + }); + }); + + suite('undefined', () => { + test('type', () => { + assert.doesNotThrow(() => { + assertOneOf( + undefined, + [undefined], + 'undefined must be one of: undefined', + ); + }); + + assert.doesNotThrow(() => { + assertOneOf( + undefined, + [void 0], + 'undefined must be one of: void 0', + ); + }); + }); + + test('subtype', () => { + assert.doesNotThrow(() => { + let item: undefined | null; + const list: (undefined)[] = [undefined]; + + assertOneOf( + item, + list, + 'undefined | null must be one of: undefined', + ); + + typeCheck(item); + }); + }); + }); + + suite('null', () => { + test('type', () => { + assert.doesNotThrow(() => { + assertOneOf( + null, + [null], + 'null must be one of: null', + ); + }); + }); + + test('subtype', () => { + assert.doesNotThrow(() => { + const item: undefined | null | string = null; + const list: (null)[] = [null]; + + assertOneOf( + item, + list, + 'null must be one of: null', + ); + + typeCheck(item); + }); + }); + }); + + suite('any', () => { + test('item', () => { + assert.doesNotThrow(() => { + const item: any = '1'; + const list: ('1' | '2')[] = ['2', '1']; + + assertOneOf( + item, + list, + '1 must be one of: 2, 1', + ); + + typeCheck<'1' | '2'>(item); + }); + }); + + test('list', () => { + assert.doesNotThrow(() => { + const item: '5' = '5'; + const list: any[] = ['3', '5', '2.5']; + + assertOneOf( + item, + list, + '5 must be one of: 3, 5, 2.5', + ); + + typeCheck<'5'>(item); + }); + }); + + test('both', () => { + assert.doesNotThrow(() => { + const item: any = '12'; + const list: any[] = ['14.25', '7', '12']; + + assertOneOf( + item, + list, + '12 must be one of: 14.25, 7, 12', + ); + + typeCheck(item); + }); + }); + }); + + suite('unknown', () => { + test('item', () => { + assert.doesNotThrow(() => { + const item: unknown = '1'; + const list: ('1' | '2')[] = ['2', '1']; + + assertOneOf( + item, + list, + '1 must be one of: 2, 1', + ); + + typeCheck<'1' | '2'>(item); + }); + }); + + test('both', () => { + assert.doesNotThrow(() => { + const item: unknown = '12'; + const list: unknown[] = ['14.25', '7', '12']; + + assertOneOf( + item, + list, + '12 must be one of: 14.25, 7, 12', + ); + + typeCheck(item); + }); + }); + }); + }); + + suite('failure', () => { + suite('string', () => { + test('type', () => { + assert.throws(() => { + assertOneOf( + 'baz', + ['foo', 'bar'], + 'Baz must not be one of: foo, bar', + ); + }); + }); + + test('subtype', () => { + assert.throws(() => { + const item: string = 'vitannia'; + const list: ('hi' | 'ciao' | 'hola')[] = ['hi', 'ciao']; + + assertOneOf( + item, + list, + 'vitannia must be one of: hi, ciao', + ); + }); + }); + + test('empty', () => { + assert.throws(() => { + const item: string = 'vitannia'; + const list: ('hi' | 'ciao' | 'hola')[] = []; + + assertOneOf( + item, + list, + 'vitannia must be one of: empty', + ); + }); + }); + }); + + suite('number', () => { + test('type', () => { + assert.throws(() => { + assertOneOf( + 19, + [10, 100], + '19 must not be one of: 10, 100', + ); + }); + }); + + test('subtype', () => { + assert.throws(() => { + const item: number = 24; + const list: (20 | 2000)[] = [20, 2000]; + + assertOneOf( + item, + list, + '24 must not be one of: 20, 2000', + ); + }); + }); + + test('empty', () => { + assert.throws(() => { + const item: number = 20; + const list: (20 | 2000)[] = []; + + assertOneOf( + item, + list, + '20 must not be one of: empty', + ); + }); + }); + }); + + suite('boolean', () => { + test('type', () => { + assert.throws(() => { + assertOneOf( + true, + [false], + 'true must not be one of: false', + ); + }); + + assert.throws(() => { + assertOneOf( + false, + [true], + 'false must not be one of: true', + ); + }); + }); + + test('subtype (true)', () => { + assert.throws(() => { + const item: boolean = true; + const list: (true | false)[] = [false]; + + assertOneOf( + item, + list, + 'true must not be one of: false', + ); + }); + }); + + test('subtype (false)', () => { + assert.throws(() => { + const item: boolean = false; + const list: (false | true)[] = [true, true, true]; + + assertOneOf( + item, + list, + 'false must be one of: true, true, true', + ); + }); + }); + + test('empty', () => { + assert.throws(() => { + const item: boolean = true; + const list: (false | true)[] = []; + + assertOneOf( + item, + list, + 'true must be one of: empty', + ); + }); + }); + }); + + suite('undefined', () => { + test('type', () => { + assert.throws(() => { + assertOneOf( + undefined, + [], + 'undefined must not be one of: empty', + ); + }); + + assert.throws(() => { + assertOneOf( + void 0, + [], + 'void 0 must not be one of: empty', + ); + }); + }); + + test('subtype', () => { + assert.throws(() => { + let item: undefined | null; + const list: (undefined | null)[] = [null]; + + assertOneOf( + item, + list, + 'undefined must be one of: null', + ); + }); + }); + + test('empty', () => { + assert.throws(() => { + let item: undefined | null; + const list: (undefined | null)[] = []; + + assertOneOf( + item, + list, + 'undefined must be one of: empty', + ); + }); + }); + }); + + suite('null', () => { + test('type', () => { + assert.throws(() => { + assertOneOf( + null, + [], + 'null must be one of: empty', + ); + }); + }); + + test('subtype', () => { + assert.throws(() => { + const item: undefined | null | string = null; + const list: null[] = []; + + assertOneOf( + item, + list, + 'null must be one of: empty', + ); + }); + }); + }); + + suite('any', () => { + test('item', () => { + assert.throws(() => { + const item: any = '1'; + const list: ('1' | '2' | '3' | '4')[] = ['3', '4']; + + assertOneOf( + item, + list, + '1 must not be one of: 3, 4', + ); + }); + }); + + test('list', () => { + assert.throws(() => { + const item: '5' = '5'; + const list: any[] = ['3', '6', '2.5']; + + assertOneOf( + item, + list, + '5 must not be one of: 3, 6, 2.5', + ); + }); + }); + + test('both', () => { + assert.throws(() => { + const item: any = '12'; + const list: any[] = ['14.25', '7', '15']; + + assertOneOf( + item, + list, + '12 must not be one of: 14.25, 7, 15', + ); + }); + }); + + test('empty', () => { + assert.throws(() => { + const item: any = '25'; + const list: any[] = []; + + assertOneOf( + item, + list, + '25 must not be one of: empty', + ); + }); + }); + }); + + suite('unknown', () => { + test('item', () => { + assert.throws(() => { + const item: unknown = '100'; + const list: ('11' | '12')[] = ['12', '11']; + + assertOneOf( + item, + list, + '100 must not be one of: 12, 11', + ); + + }); + + test('both', () => { + assert.throws(() => { + const item: unknown = '21'; + const list: unknown[] = ['14.25', '7', '12']; + + assertOneOf( + item, + list, + '21 must not be one of: 14.25, 7, 12', + ); + + }); + }); + }); + }); + }); + }); + test('validateConstraints', () => { types.validateConstraints([1, 'test', true], [Number, String, Boolean]); types.validateConstraints([1, 'test', true], ['number', 'string', 'boolean']); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 824ba88a2f45..47c41d56362b 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -82,6 +82,7 @@ import { ChatQuotasService, ChatQuotasStatusBarEntry, IChatQuotasService } from import { ChatSetupContribution } from './chatSetup.js'; import { ChatEditorOverlayController } from './chatEditing/chatEditingEditorOverlay.js'; import '../common/promptSyntax/languageFeatures/promptLinkProvider.js'; +import '../common/promptSyntax/languageFeatures/promptPathAutocompletion.js'; import { PromptFilesConfig } from '../common/promptSyntax/config.js'; import { BuiltinToolsContribution } from '../common/tools/tools.js'; import { IWorkbenchAssignmentService } from '../../../services/assignment/common/assignmentService.js'; diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptLinkProvider.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptLinkProvider.ts index 34bb7f530304..9030c7c00e68 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptLinkProvider.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptLinkProvider.ts @@ -93,6 +93,6 @@ export class PromptLinkProvider extends Disposable implements LinkProvider { } } -// register this provider as a workbench contribution +// register the provider as a workbench contribution Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(PromptLinkProvider, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptPathAutocompletion.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptPathAutocompletion.ts new file mode 100644 index 000000000000..1dac14fd1e06 --- /dev/null +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/languageFeatures/promptPathAutocompletion.ts @@ -0,0 +1,362 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Notes on what to implement next: + * - re-trigger suggestions dialog on `folder` selection because the `#file:` references take + * `file` paths, therefore a "folder" completion is never final + * - provide the same suggestions that the `#file:` variables in the chat input have, e.g., + * recently used files, related files, etc. + * - support markdown links; markdown extension does sometimes provide the paths completions, but + * the prompt completions give more options (e.g., recently used files, related files, etc.) + * - add `Windows` support + */ + +import { LANGUAGE_SELECTOR } from '../constants.js'; +import { IPromptSyntaxService } from '../service/types.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { IPromptFileReference } from '../parsers/types.js'; +import { FileReference } from '../codecs/tokens/fileReference.js'; +import { assertOneOf } from '../../../../../../base/common/types.js'; +import { isWindows } from '../../../../../../base/common/platform.js'; +import { ITextModel } from '../../../../../../editor/common/model.js'; +import { Disposable } from '../../../../../../base/common/lifecycle.js'; +import { CancellationError } from '../../../../../../base/common/errors.js'; +import { Position } from '../../../../../../editor/common/core/position.js'; +import { dirname, extUri } from '../../../../../../base/common/resources.js'; +import { assert, assertNever } from '../../../../../../base/common/assert.js'; +import { IFileService } from '../../../../../../platform/files/common/files.js'; +import { CancellationToken } from '../../../../../../base/common/cancellation.js'; +import { Registry } from '../../../../../../platform/registry/common/platform.js'; +import { LifecyclePhase } from '../../../../../services/lifecycle/common/lifecycle.js'; +import { ILanguageFeaturesService } from '../../../../../../editor/common/services/languageFeatures.js'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from '../../../../../common/contributions.js'; +import { CompletionContext, CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList } from '../../../../../../editor/common/languages.js'; + +/** + * Type for a filesystem completion item - the one that has its {@link CompletionItem.kind kind} set + * to either {@link CompletionItemKind.File} or {@link CompletionItemKind.Folder}. + */ +type TFilesystemCompletionItem = CompletionItem & { kind: CompletionItemKind.File | CompletionItemKind.Folder }; + +/** + * Type for a "raw" folder suggestion. Unlike the full completion item, + * this one does not have `insertText` and `range` properties which are + * meant to be added later. + */ +type TFolderSuggestion = Omit & { label: string }; + +/** + * Type for trigger characters handled by this autocompletion provider. + */ +type TTriggerCharacter = ':' | '.' | '/'; + +/** + * Finds a file reference that suites the provided `position`. + */ +const findFileReference = ( + references: readonly IPromptFileReference[], + position: Position, +): IPromptFileReference | undefined => { + for (const reference of references) { + const { range } = reference; + + // ignore any other types of references + if (reference.type !== 'file') { + return undefined; + } + + // this ensures that we handle only the `#file:` references for now + if (!reference.text.startsWith(FileReference.TOKEN_START)) { + return undefined; + } + + // reference must match the provided position + const { startLineNumber, endColumn } = range; + if ((startLineNumber !== position.lineNumber) || (endColumn !== position.column)) { + continue; + } + + return reference; + } + + return undefined; +}; + +/** + * Provides reference paths autocompletion for the `#file:` variables inside prompts. + */ +export class PromptPathAutocompletion extends Disposable implements CompletionItemProvider { + /** + * Debug display name for this provider. + */ + public readonly _debugDisplayName: string = 'PromptPathAutocompletion'; + + /** + * List of trigger characters handled by this provider. + */ + public readonly triggerCharacters: TTriggerCharacter[] = [':', '.', '/']; + + constructor( + @IFileService private readonly fileService: IFileService, + @IPromptSyntaxService private readonly promptSyntaxService: IPromptSyntaxService, + @ILanguageFeaturesService private readonly languageService: ILanguageFeaturesService, + ) { + super(); + + this.languageService.completionProvider.register(LANGUAGE_SELECTOR, this); + } + + /** + * The main function of this provider that calculates + * completion items based on the provided arguments. + */ + public async provideCompletionItems( + model: ITextModel, + position: Position, + context: CompletionContext, + token: CancellationToken, + ): Promise { + assert( + !token.isCancellationRequested, + new CancellationError(), + ); + + const { triggerCharacter } = context; + + // it must always have been triggered by a character + if (!triggerCharacter) { + return undefined; + } + + assertOneOf( + triggerCharacter, + this.triggerCharacters, + `Prompt path autocompletion provider`, + ); + + const parser = this.promptSyntaxService.getParserFor(model); + assert( + !parser.disposed, + 'Prompt parser must not be disposed.', + ); + + // start the parser in case it was not started yet, + // and wait for it to settle to a final result + const { references } = await parser + .start() + .settled(); + + // validate that the cancellation was not yet requested + assert( + !token.isCancellationRequested, + new CancellationError(), + ); + + const fileReference = findFileReference(references, position); + if (!fileReference) { + return undefined; + } + + const modelDirname = dirname(model.uri); + + // in the case of the '.' trigger character, we must check if this is the first + // dot in the link path, otherwise the dot could be a part of a folder name + if (triggerCharacter === ':' || (triggerCharacter === '.' && fileReference.path === '.')) { + return { + suggestions: await this.getFirstFolderSuggestions( + triggerCharacter, + modelDirname, + fileReference, + ), + }; + } + + if (triggerCharacter === '/' || triggerCharacter === '.') { + return { + suggestions: await this.getNonFirstFolderSuggestions( + triggerCharacter, + modelDirname, + fileReference, + ), + }; + } + + assertNever( + triggerCharacter, + `Unexpected trigger character '${triggerCharacter}'.`, + ); + } + + /** + * Gets "raw" folder suggestions. Unlike the full completion items, + * these ones do not have `insertText` and `range` properties which + * are meant to be added by the caller later on. + */ + private async getFolderSuggestions( + uri: URI, + ): Promise { + const { children } = await this.fileService.resolve(uri); + const suggestions: TFolderSuggestion[] = []; + + // no `children` - no suggestions + if (!children) { + return suggestions; + } + + for (const child of children) { + const kind = child.isDirectory + ? CompletionItemKind.Folder + : CompletionItemKind.File; + + const sortText = child.isDirectory + ? '1' + : '2'; + + suggestions.push({ + label: child.name, + kind, + sortText, + }); + } + + return suggestions; + } + + /** + * Gets suggestions for a first folder/file name in the path. E.g., the one + * that follows immediately after the `:` character of the `#file:` variable. + * + * The main difference between this and "subsequent" folder cases is that in + * the beginning of the path the suggestions also contain the `..` item and + * the `./` normalization prefix for relative paths. + * + * See also {@link getNonFirstFolderSuggestions}. + */ + private async getFirstFolderSuggestions( + character: ':' | '.', + fileFolderUri: URI, + fileReference: IPromptFileReference, + ): Promise { + const { linkRange } = fileReference; + + // when character is `:`, there must be no link present yet + // otherwise the `:` was used in the middle of the link hence + // we don't want to provide suggestions for that + if (character === ':' && linkRange !== undefined) { + return []; + } + + // otherwise when the `.` character is present, it is inside the link part + // of the reference, hence we always expect the link range to be present + if (character === '.' && linkRange === undefined) { + return []; + } + + const suggestions = await this.getFolderSuggestions(fileFolderUri); + + // replacement range of the suggestions + // when character is `.` we want to also replace it, because we add + // the `./` at the beginning of all the relative paths + const startColumnOffset = (character === '.') ? 1 : 0; + const range = { + ...fileReference.range, + endColumn: fileReference.range.endColumn, + startColumn: fileReference.range.endColumn - startColumnOffset, + }; + + return [ + { + label: '..', + kind: CompletionItemKind.Folder, + insertText: '..', + range, + sortText: '0', + }, + ...suggestions + .map((suggestion) => { + // add space at the end of file names since no completions + // that follow the file name are expected anymore + const suffix = (suggestion.kind === CompletionItemKind.File) + ? ' ' + : ''; + + return { + ...suggestion, + range, + label: `./${suggestion.label}${suffix}`, + // we use the `./` prefix for consistency + insertText: `./${suggestion.label}${suffix}`, + }; + }), + ]; + } + + /** + * Gets suggestions for a folder/file name that follows after the first one. + * See also {@link getFirstFolderSuggestions}. + */ + private async getNonFirstFolderSuggestions( + character: '/' | '.', + fileFolderUri: URI, + fileReference: IPromptFileReference, + ): Promise { + const { linkRange, path } = fileReference; + + if (linkRange === undefined) { + return []; + } + + const currenFolder = extUri.resolvePath(fileFolderUri, path); + let suggestions = await this.getFolderSuggestions(currenFolder); + + // when trigger character was a `.`, which is we know is inside + // the folder/file name in the path, filter out to only items + // that start with the dot instead of showing all of them + if (character === '.') { + suggestions = suggestions.filter((suggestion) => { + return suggestion.label.startsWith('.'); + }); + } + + // replacement range of the suggestions + // when character is `.` we want to also replace it too + const startColumnOffset = (character === '.') ? 1 : 0; + const range = { + ...fileReference.range, + endColumn: fileReference.range.endColumn, + startColumn: fileReference.range.endColumn - startColumnOffset, + }; + + return suggestions + .map((suggestion) => { + // add space at the end of file names since no completions + // that follow the file name are expected anymore + const suffix = (suggestion.kind === CompletionItemKind.File) + ? ' ' + : ''; + + return { + ...suggestion, + insertText: `${suggestion.label}${suffix}`, + range, + }; + }); + } +} + +/** + * We restrict this provider to `Unix` machines for now because of + * the filesystem paths differences on `Windows` operating system. + * + * Notes on `Windows` support: + * - we add the `./` for the first path component, which may not work on `Windows` + * - the first path component of the absolute paths must be a drive letter + */ +if (!isWindows) { + // register the provider as a workbench contribution + Registry.as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(PromptPathAutocompletion, LifecyclePhase.Eventually); +} diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.d.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.d.ts index 91aef1479f12..586b134d8e1f 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.d.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.d.ts @@ -55,6 +55,16 @@ export interface IPromptReference extends IDisposable { */ readonly linkRange: IRange | undefined; + /** + * Text of the reference as it appears in the source. + */ + readonly text: string; + + /** + * Original link path as it appears in the source. + */ + readonly path: string; + /** * Whether the current reference points to a prompt snippet file. */ From 77d6d7111815d41b049fd553ceacab1e274be1e9 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 4 Feb 2025 18:30:12 -0800 Subject: [PATCH 0375/2632] Default keybinding for change cell language. (#239652) --- .../contrib/notebook/browser/controller/editActions.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index f613e034b6aa..3fe9771b282d 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; +import { KeyChord, KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; import { Mimes } from '../../../../../base/common/mime.js'; import { URI } from '../../../../../base/common/uri.js'; import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js'; @@ -361,6 +361,11 @@ registerAction2(class ChangeCellLanguageAction extends NotebookCellAction Date: Tue, 4 Feb 2025 18:27:55 -0800 Subject: [PATCH 0376/2632] add unit tests for the `TextModelPromptParser` class --- .../common/promptSyntax/parsers/types.d.ts | 11 +- .../parsers/textModelPromptParser.test.ts | 261 ++++++++++++++++++ .../promptSyntax/testUtils/createUri.ts | 19 ++ .../testUtils/expectedReference.ts | 155 +++++++++++ 4 files changed, 443 insertions(+), 3 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts create mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/createUri.ts create mode 100644 src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/expectedReference.ts diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.d.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.d.ts index 586b134d8e1f..325e5c3ec451 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.d.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/parsers/types.d.ts @@ -99,17 +99,22 @@ export interface IPromptReference extends IDisposable { */ readonly topError: IResolveError | undefined; + /** + * Direct references of the current reference. + */ + references: readonly IPromptReference[]; + /** * All references that the current reference may have, - * including the all possible nested child references. + * including all possible nested child references. */ allReferences: readonly IPromptReference[]; /** * All *valid* references that the current reference may have, - * including the all possible nested child references. + * including all possible nested child references. * - * A valid reference is the one that points to an existing resource, + * A valid reference is one that points to an existing resource, * without creating a circular reference loop or having any other * issues that would make the reference resolve logic to fail. */ diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts new file mode 100644 index 000000000000..430824a9abe8 --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts @@ -0,0 +1,261 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { createURI } from '../testUtils/createUri.js'; +import { URI } from '../../../../../../../base/common/uri.js'; +import { Schemas } from '../../../../../../../base/common/network.js'; +import { ExpectedReference } from '../testUtils/expectedReference.js'; +import { ITextModel } from '../../../../../../../editor/common/model.js'; +import { Disposable } from '../../../../../../../base/common/lifecycle.js'; +import { FileOpenFailed } from '../../../../common/promptFileReferenceErrors.js'; +import { IFileService } from '../../../../../../../platform/files/common/files.js'; +import { randomBoolean } from '../../../../../../../base/test/common/testUtils.js'; +import { FileService } from '../../../../../../../platform/files/common/fileService.js'; +import { createTextModel } from '../../../../../../../editor/test/common/testTextModel.js'; +import { ILogService, NullLogService } from '../../../../../../../platform/log/common/log.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js'; +import { TextModelPromptParser } from '../../../../common/promptSyntax/parsers/textModelPromptParser.js'; +import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'; +import { InMemoryFileSystemProvider } from '../../../../../../../platform/files/common/inMemoryFilesystemProvider.js'; +import { TestInstantiationService } from '../../../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; + +/** + * Test helper to run unit tests for the {@link TextModelPromptParser} + * class using different test input parameters + */ +class TextModelPromptParserTest extends Disposable { + public readonly model: ITextModel; + public readonly parser: TextModelPromptParser; + + constructor( + uri: URI, + initialContents: string[], + @IFileService fileService: IFileService, + @IInstantiationService initService: IInstantiationService, + ) { + super(); + + // create in-memory file system for this test instance + const fileSystemProvider = this._register(new InMemoryFileSystemProvider()); + this._register(fileService.registerProvider(Schemas.file, fileSystemProvider)); + + // both line endings should yield the same results + const lineEnding = (randomBoolean()) ? '\r\n' : '\n'; + + // create the underlying model + this.model = this._register( + createTextModel( + initialContents.join(lineEnding), + 'fooLang', + undefined, + uri, + ), + ); + + // create the parser instance + this.parser = this._register( + initService.createInstance(TextModelPromptParser, this.model, []), + ).start(); + } + + /** + * Validate the current state of the parser. + */ + public async validateReferences( + expectedReferences: readonly ExpectedReference[], + ) { + await this.parser.allSettled(); + + const { references } = this.parser; + for (let i = 0; i < expectedReferences.length; i++) { + expectedReferences[i].validateEqual(references[i]); + } + + assert.strictEqual( + expectedReferences.length, + references.length, + `[${this.model.uri}] Unexpected number of references.`, + ); + } +} + +suite('TextModelPromptParser', () => { + const disposables = ensureNoDisposablesAreLeakedInTestSuite(); + + let instantiationService: TestInstantiationService; + + setup(async () => { + instantiationService = disposables.add(new TestInstantiationService()); + instantiationService.stub(ILogService, new NullLogService()); + instantiationService.stub(IFileService, disposables.add(instantiationService.createInstance(FileService))); + }); + + /** + * Create a new test instance with provided input parameters. + */ + const createTest = ( + uri: URI, + initialContents: string[], + ): TextModelPromptParserTest => { + return disposables.add( + instantiationService.createInstance( + TextModelPromptParserTest, + uri, + initialContents, + ), + ); + }; + + test('core logic #1', async () => { + const test = createTest( + createURI('/foo/bar.md'), + [ + /* 01 */"The quick brown fox tries #file:/abs/path/to/file.md online yoga for the first time.", + /* 02 */"Maria discovered a stray turtle roaming in her kitchen.", + /* 03 */"Why did the robot write a poem about existential dread?", + /* 04 */"Sundays are made for two things: pancakes and procrastination.", + /* 05 */"Sometimes, the best code is the one you never have to write.", + /* 06 */"A lone kangaroo once hopped into the local cafe, seeking free Wi-Fi.", + /* 07 */"Critical #file:./folder/binary.file thinking is like coffee; best served strong [md link](/etc/hosts/random-file.txt) and without sugar.", + /* 08 */"Music is the mind’s way of doodling in the air.", + /* 09 */"Stargazing is just turning your eyes into cosmic explorers.", + /* 10 */"Never trust a balloon salesman who hates birthdays.", + /* 11 */"Running backward can be surprisingly enlightening.", + /* 12 */"There’s an art to whispering loudly.", + ], + ); + + await test.validateReferences([ + new ExpectedReference({ + uri: createURI('/abs/path/to/file.md'), + text: '#file:/abs/path/to/file.md', + path: '/abs/path/to/file.md', + startLine: 1, + startColumn: 27, + pathStartColumn: 33, + childrenOrError: new FileOpenFailed(createURI('/abs/path/to/file.md'), 'File not found.'), + }), + new ExpectedReference({ + uri: createURI('/foo/folder/binary.file'), + text: '#file:./folder/binary.file', + path: './folder/binary.file', + startLine: 7, + startColumn: 10, + pathStartColumn: 16, + childrenOrError: new FileOpenFailed(createURI('/foo/folder/binary.file'), 'File not found.'), + }), + new ExpectedReference({ + uri: createURI('/etc/hosts/random-file.txt'), + text: '[md link](/etc/hosts/random-file.txt)', + path: '/etc/hosts/random-file.txt', + startLine: 7, + startColumn: 81, + pathStartColumn: 91, + childrenOrError: new FileOpenFailed(createURI('/etc/hosts/random-file.txt'), 'File not found.'), + }), + ]); + }); + + test('core logic #2', async () => { + const test = createTest( + createURI('/absolute/folder/and/a/filename.txt'), + [ + /* 01 */"The penguin wore sunglasses but never left the iceberg.", + /* 02 */"I once saw a cloud that looked like an antique teapot.", + /* 03 */"Midnight snacks are the secret to eternal [link text](./foo-bar-baz/another-file.ts) happiness.", + /* 04 */"A stray sock in the hallway is a sign of chaotic creativity.", + /* 05 */"Dogs dream in colorful squeaks and belly rubs.", + /* 06 */"Never [caption](../../../c/file_name.prompt.md)\t underestimate the power of a well-timed nap.", + /* 07 */"The cactus on my desk has a thriving Instagram account.", + /* 08 */"In an alternate universe, pigeons deliver sushi by drone.", + /* 09 */"Lunar rainbows only appear when you sing in falsetto.", + /* 10 */"Carrots have secret telepathic abilities, but only on Tuesdays.", + /* 11 */"Sometimes, the best advice comes \t\t#file:../../main.rs\t#file:./somefolder/../samefile.jpeg\tfrom a talking dishwasher.", + /* 12 */"Paper airplanes believe they can fly until proven otherwise.", + /* 13 */"A library without stories is just a room full of silent trees.", + /* 14 */"The invisible cat meows only when it sees a postman.", + /* 15 */"Code reviews are like detective novels without the plot twists." + ], + ); + + await test.validateReferences([ + new ExpectedReference({ + uri: createURI('/absolute/folder/and/a/foo-bar-baz/another-file.ts'), + text: '[link text](./foo-bar-baz/another-file.ts)', + path: './foo-bar-baz/another-file.ts', + startLine: 3, + startColumn: 43, + pathStartColumn: 55, + childrenOrError: new FileOpenFailed(createURI('/absolute/folder/and/a/foo-bar-baz/another-file.ts'), 'File not found.'), + }), + new ExpectedReference({ + uri: createURI('/absolute/c/file_name.prompt.md'), + text: '[caption](../../../c/file_name.prompt.md)', + path: '../../../c/file_name.prompt.md', + startLine: 6, + startColumn: 7, + pathStartColumn: 17, + childrenOrError: new FileOpenFailed(createURI('/absolute/c/file_name.prompt.md'), 'File not found.'), + }), + new ExpectedReference({ + uri: createURI('/absolute/folder/main.rs'), + text: '#file:../../main.rs', + path: '../../main.rs', + startLine: 11, + startColumn: 36, + pathStartColumn: 42, + childrenOrError: new FileOpenFailed(createURI('/absolute/folder/main.rs'), 'File not found.'), + }), + new ExpectedReference({ + uri: createURI('/absolute/folder/and/a/samefile.jpeg'), + text: '#file:./somefolder/../samefile.jpeg', + path: './somefolder/../samefile.jpeg', + startLine: 11, + startColumn: 56, + pathStartColumn: 62, + childrenOrError: new FileOpenFailed(createURI('/absolute/folder/and/a/samefile.jpeg'), 'File not found.'), + }), + ]); + }); + + test('gets disposed with the model', async () => { + const test = createTest( + createURI('/some/path/file.prompt.md'), + [ + 'line1', + 'line2', + 'line3', + ], + ); + + // no references in the model contents + await test.validateReferences([]); + + test.model.dispose(); + + assert( + test.parser.disposed, + 'The parser should be disposed with its model.', + ); + }); + + test('toString() implementation', async () => { + const test = createTest( + createURI('/Users/legomushroom/repos/prompt-snippets/README.md'), + [ + 'line1', + 'line2', + 'line3', + ], + ); + + assert.strictEqual( + test.parser.toString(), + 'text-model-prompt:/Users/legomushroom/repos/prompt-snippets/README.md', + 'The parser should provide correct `toString()` implementation.', + ); + }); +}); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/createUri.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/createUri.ts new file mode 100644 index 000000000000..a5bf124484aa --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/createUri.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from '../../../../../../../base/common/uri.js'; +import { isWindows } from '../../../../../../../base/common/platform.js'; + +/** + * Creates cross-platform URI for testing purposes. + * On `Windows`, absolute paths are prefixed with the disk name. + */ +export const createURI = (linkPath: string): URI => { + if (isWindows && linkPath.startsWith('/')) { + return URI.file('/d:' + linkPath); + } + + return URI.file(linkPath); +}; diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/expectedReference.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/expectedReference.ts new file mode 100644 index 000000000000..6fd8283b34ed --- /dev/null +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/expectedReference.ts @@ -0,0 +1,155 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { URI } from '../../../../../../../base/common/uri.js'; +import { Range } from '../../../../../../../editor/common/core/range.js'; +import { assertDefined } from '../../../../../../../base/common/types.js'; +import { ParseError } from '../../../../common/promptFileReferenceErrors.js'; +import { IPromptFileReference } from '../../../../common/promptSyntax/parsers/types.js'; +import { TErrorCondition } from '../../../../common/promptSyntax/parsers/basePromptParser.js'; + +/** + * Options for the {@link ExpectedReference} class. + */ +interface IExpectedReferenceOptions { + readonly uri: URI; + readonly text: string; + readonly path: string; + readonly startLine: number; + readonly startColumn: number; + readonly pathStartColumn: number; + readonly childrenOrError?: TErrorCondition | (ExpectedReference[]); +} + +/** + * An expected child reference to use in tests. + */ +export class ExpectedReference { + constructor(private readonly options: IExpectedReferenceOptions) { } + + /** + * Validate that the provided reference is equal to this object. + */ + public validateEqual(other: IPromptFileReference) { + const { uri, text, path, childrenOrError = [] } = this.options; + const errorPrefix = `[${uri}] `; + + /** + * Validate the base properties of the reference first. + */ + + assert.strictEqual( + other.uri.toString(), + uri.toString(), + `${errorPrefix} Incorrect 'uri'.`, + ); + + assert.strictEqual( + other.text, + text, + `${errorPrefix} Incorrect 'text'.`, + ); + + assert.strictEqual( + other.path, + path, + `${errorPrefix} Incorrect 'path'.`, + ); + + const range = new Range( + this.options.startLine, + this.options.startColumn, + this.options.startLine, + this.options.startColumn + text.length, + ); + + assert( + range.equalsRange(other.range), + `${errorPrefix} Incorrect 'range': expected '${range}', got '${other.range}'.`, + ); + + if (path.length) { + assertDefined( + other.linkRange, + `${errorPrefix} Link range must be defined.`, + ); + + const linkRange = new Range( + this.options.startLine, + this.options.pathStartColumn, + this.options.startLine, + this.options.pathStartColumn + path.length, + ); + + assert( + linkRange.equalsRange(other.linkRange), + `${errorPrefix} Incorrect 'linkRange': expected '${linkRange}', got '${other.linkRange}'.`, + ); + } else { + assert.strictEqual( + other.linkRange, + undefined, + `${errorPrefix} Link range must be 'undefined'.`, + ); + } + + /** + * Next validate children or error condition. + */ + + if (childrenOrError instanceof ParseError) { + const error = childrenOrError; + const { errorCondition } = other; + assertDefined( + errorCondition, + `${errorPrefix} Expected 'errorCondition' to be defined.`, + ); + + assert( + errorCondition instanceof ParseError, + `${errorPrefix} Expected 'errorCondition' to be a 'ParseError'.`, + ); + + assert( + error.sameTypeAs(errorCondition), + `${errorPrefix} Incorrect 'errorCondition' type.`, + ); + + return; + } + + const children = childrenOrError; + const { references } = other; + + for (let i = 0; i < children.length; i++) { + children[i].validateEqual(references[i]); + } + + if (references.length > children.length) { + const extraReference = references[children.length]; + + // sanity check + assertDefined( + extraReference, + `${errorPrefix} Extra reference must be defined.`, + ); + + throw new Error(`${errorPrefix} Expected no more references, got '${extraReference.text}'.`); + } + + if (children.length > references.length) { + const expectedReference = children[references.length]; + + // sanity check + assertDefined( + expectedReference, + `${errorPrefix} Expected reference must be defined.`, + ); + + throw new Error(`${errorPrefix} Expected another reference '${expectedReference.options.text}', got 'undefined'.`); + } + } +} From 19262a9bdcf5fca76b29064b7b9709792ed34b4f Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Tue, 4 Feb 2025 18:55:11 -0800 Subject: [PATCH 0377/2632] fix unit tests on `Windows` add more doc comments --- .../parsers/textModelPromptParser.test.ts | 12 +++++-- .../testUtils/expectedReference.ts | 32 ++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts index 430824a9abe8..e6c088257d65 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts @@ -27,7 +27,14 @@ import { TestInstantiationService } from '../../../../../../../platform/instanti * class using different test input parameters */ class TextModelPromptParserTest extends Disposable { + /** + * Underlying text model of the parser. + */ public readonly model: ITextModel; + + /** + * The parser instance. + */ public readonly parser: TextModelPromptParser; constructor( @@ -243,8 +250,9 @@ suite('TextModelPromptParser', () => { }); test('toString() implementation', async () => { + const modelUri = createURI('/Users/legomushroom/repos/prompt-snippets/README.md'); const test = createTest( - createURI('/Users/legomushroom/repos/prompt-snippets/README.md'), + modelUri, [ 'line1', 'line2', @@ -254,7 +262,7 @@ suite('TextModelPromptParser', () => { assert.strictEqual( test.parser.toString(), - 'text-model-prompt:/Users/legomushroom/repos/prompt-snippets/README.md', + `text-model-prompt:${modelUri}`, 'The parser should provide correct `toString()` implementation.', ); }); diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/expectedReference.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/expectedReference.ts index 6fd8283b34ed..45bd8552489d 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/expectedReference.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/expectedReference.ts @@ -15,13 +15,43 @@ import { TErrorCondition } from '../../../../common/promptSyntax/parsers/basePro * Options for the {@link ExpectedReference} class. */ interface IExpectedReferenceOptions { + /** + * Final `URI` of the reference. + */ readonly uri: URI; + + /** + * Full text of the reference as it appears in the source text. + */ readonly text: string; + + /** + * The `path` part of the reference (e.g., the `/abs/path/to/file.md` + * part of the `[](/abs/path/to/file.md)` reference). + */ readonly path: string; + + /** + * Start line of the reference in the source text. Because links cannot + * contain line breaks, the end line number is also equal to this value. + */ readonly startLine: number; + + /** + * Start column of the full reference text as it appears in the source text. + */ readonly startColumn: number; + + /** + * Start column number of the `path` part of the reference. + */ readonly pathStartColumn: number; - readonly childrenOrError?: TErrorCondition | (ExpectedReference[]); + + /** + * Either an `error` that was generated during attempt to resolve this reference, + * or a list of expected child references if the attempt was successful. + */ + readonly childrenOrError?: TErrorCondition | ExpectedReference[]; } /** From 95bafc078d71735d4c1e2ef6e490cd43df59315c Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Tue, 4 Feb 2025 19:09:30 -0800 Subject: [PATCH 0378/2632] fix `toString()` unit test of the `TextModelpromptParser` --- .../common/promptSyntax/parsers/textModelPromptParser.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts index e6c088257d65..95ec4f3bd17a 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/parsers/textModelPromptParser.test.ts @@ -262,7 +262,7 @@ suite('TextModelPromptParser', () => { assert.strictEqual( test.parser.toString(), - `text-model-prompt:${modelUri}`, + `text-model-prompt:${modelUri.path}`, 'The parser should provide correct `toString()` implementation.', ); }); From c363306526528d2b39bc71cc5df95760b11c057f Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 5 Feb 2025 14:18:05 +0800 Subject: [PATCH 0379/2632] refactor: reduce references to global editing session (#239660) * refactor: reduce references to global editing session * refactor: rework related files contrib to support multiple editing sessions * refactor: handle multiple editing sessions in `chatContextActions.ts` --- .../chat/browser/actions/chatClearActions.ts | 19 +++--- .../browser/actions/chatContextActions.ts | 19 ++++-- .../browser/actions/chatExecuteActions.ts | 17 ++--- .../browser/chatEditing/chatEditingActions.ts | 8 ++- .../contrib/chatInputRelatedFilesContrib.ts | 64 +++++++++---------- 5 files changed, 63 insertions(+), 64 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index 57a57061aed5..52c81f2fbf39 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -17,8 +17,9 @@ import { IViewsService } from '../../../../services/views/common/viewsService.js import { isChatViewTitleActionContext } from '../../common/chatActions.js'; import { ChatAgentLocation } from '../../common/chatAgents.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js'; -import { ChatViewId, EditsViewId, IChatWidgetService } from '../chat.js'; +import { hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../../common/chatEditingService.js'; +import { ChatViewId, EditsViewId, IChatWidget, IChatWidgetService } from '../chat.js'; +import { EditingSessionAction } from '../chatEditing/chatEditingActions.js'; import { ctxIsGlobalEditingSession } from '../chatEditing/chatEditingEditorController.js'; import { ChatEditorInput } from '../chatEditorInput.js'; import { ChatViewPane } from '../chatViewPane.js'; @@ -102,7 +103,7 @@ export function registerNewChatActions() { } }); - registerAction2(class NewEditSessionAction extends Action2 { + registerAction2(class NewEditSessionAction extends EditingSessionAction { constructor() { super({ id: ACTION_ID_NEW_EDIT_SESSION, @@ -136,8 +137,7 @@ export function registerNewChatActions() { * * @returns false if the user had edits and did not action the dialog to take action on them, true otherwise */ - private async _handleCurrentEditingSession(chatEditingService: IChatEditingService, dialogService: IDialogService): Promise { - const currentEditingSession = chatEditingService.globalEditingSessionObs.get(); + private async _handleCurrentEditingSession(currentEditingSession: IChatEditingSession, dialogService: IDialogService): Promise { const currentEdits = currentEditingSession?.entries.get(); const currentEditCount = currentEdits?.length; @@ -174,14 +174,13 @@ export function registerNewChatActions() { return true; } - async run(accessor: ServicesAccessor, ...args: any[]) { + async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]) { const context = args[0]; const accessibilitySignalService = accessor.get(IAccessibilitySignalService); const widgetService = accessor.get(IChatWidgetService); - const chatEditingService = accessor.get(IChatEditingService); const dialogService = accessor.get(IDialogService); const viewsService = accessor.get(IViewsService); - if (!(await this._handleCurrentEditingSession(chatEditingService, dialogService))) { + if (!(await this._handleCurrentEditingSession(editingSession, dialogService))) { return; } if (isChatViewTitleActionContext(context)) { @@ -189,7 +188,7 @@ export function registerNewChatActions() { announceChatCleared(accessibilitySignalService); const widget = widgetService.getWidgetBySessionId(context.sessionId); if (widget) { - await chatEditingService.globalEditingSessionObs.get()?.stop(true); + await editingSession.stop(true); widget.clear(); widget.attachmentModel.clear(); widget.focusInput(); @@ -200,7 +199,7 @@ export function registerNewChatActions() { const widget = chatView.widget; announceChatCleared(accessibilitySignalService); - await chatEditingService.globalEditingSessionObs.get()?.stop(true); + await editingSession.stop(true); widget.clear(); widget.attachmentModel.clear(); widget.focusInput(); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts index f091b1600a3c..e070c3bbfd37 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts @@ -495,7 +495,7 @@ export class AttachContextAction extends Action2 { } else { // file attachment if (chatEditingService) { - chatEditingService.globalEditingSessionObs.get()?.addFileToWorkingSet(pick.resource); + getEditingSession(chatEditingService, widget)?.addFileToWorkingSet(pick.resource); } else { toAttach.push({ id: this._getFileContextId({ resource: pick.resource }), @@ -520,7 +520,7 @@ export class AttachContextAction extends Action2 { const uri = editor instanceof DiffEditorInput ? editor.modified.resource : editor.resource; if (uri) { if (chatEditingService) { - chatEditingService.globalEditingSessionObs.get()?.addFileToWorkingSet(uri); + getEditingSession(chatEditingService, widget)?.addFileToWorkingSet(uri); } else { toAttach.push({ id: this._getFileContextId({ resource: uri }), @@ -536,7 +536,7 @@ export class AttachContextAction extends Action2 { const searchView = viewsService.getViewWithId(SEARCH_VIEW_ID) as SearchView; for (const result of searchView.model.searchResult.matches()) { if (chatEditingService) { - chatEditingService.globalEditingSessionObs.get()?.addFileToWorkingSet(result.resource); + getEditingSession(chatEditingService, widget)?.addFileToWorkingSet(result.resource); } else { toAttach.push({ id: this._getFileContextId({ resource: result.resource }), @@ -575,7 +575,7 @@ export class AttachContextAction extends Action2 { }, [])); const selectedFiles = await quickInputService.pick(itemsPromise, { placeHolder: localize('relatedFiles', 'Add related files to your working set'), canPickMany: true }); for (const file of selectedFiles ?? []) { - chatEditingService?.globalEditingSessionObs.get()?.addFileToWorkingSet(file.value); + chatEditingService.getEditingSession(chatSessionId)?.addFileToWorkingSet(file.value); } } else if (isScreenshotQuickPickItem(pick)) { const blob = await hostService.getScreenshot(); @@ -778,7 +778,7 @@ export class AttachContextAction extends Action2 { }); } } else if (context.showFilesOnly) { - if (chatEditingService?.hasRelatedFilesProviders() && (widget.getInput() || chatEditingService.globalEditingSessionObs.get()?.workingSet.size)) { + if (chatEditingService?.hasRelatedFilesProviders() && (widget.getInput() || (getEditingSession(chatEditingService, widget)?.workingSet.size))) { quickPickItems.push({ kind: 'related-files', id: 'related-files', @@ -852,7 +852,7 @@ export class AttachContextAction extends Action2 { // Avoid attaching the same context twice const attachedContext = widget.attachmentModel.getAttachmentIDs(); if (chatEditingService) { - for (const [file, state] of chatEditingService.globalEditingSessionObs.get()?.workingSet.entries() ?? []) { + for (const [file, state] of getEditingSession(chatEditingService, widget)?.workingSet.entries() ?? []) { if (state.state !== WorkingSetEntryState.Suggested) { attachedContext.add(this._getFileContextId({ resource: file })); } @@ -1002,3 +1002,10 @@ const selectPromptAttachment = async (options: ISelectPromptOptions): Promise) { @@ -45,8 +49,10 @@ export abstract class EditingSessionAction extends Action2 { } run(accessor: ServicesAccessor, ...args: any[]) { + const context: IEditingSessionActionContext | undefined = args[0]; + const chatEditingService = accessor.get(IChatEditingService); - const chatWidget = accessor.get(IChatWidgetService).lastFocusedWidget; + const chatWidget = context?.widget ?? accessor.get(IChatWidgetService).lastFocusedWidget; if (chatWidget?.location !== ChatAgentLocation.EditingSession || !chatWidget.viewModel) { return; diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts index 2389cf9a3d54..6451b341dcdc 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputRelatedFilesContrib.ts @@ -7,17 +7,17 @@ import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Event } from '../../../../../base/common/event.js'; import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js'; import { ResourceMap } from '../../../../../base/common/map.js'; -import { autorun } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import { localize } from '../../../../../nls.js'; import { IWorkbenchContribution } from '../../../../common/contributions.js'; +import { ChatAgentLocation } from '../../common/chatAgents.js'; import { ChatEditingSessionChangeType, IChatEditingService, IChatEditingSession, WorkingSetEntryRemovalReason, WorkingSetEntryState } from '../../common/chatEditingService.js'; -import { IChatWidgetService } from '../chat.js'; +import { IChatWidget, IChatWidgetService } from '../chat.js'; export class ChatRelatedFilesContribution extends Disposable implements IWorkbenchContribution { static readonly ID = 'chat.relatedFilesWorkingSet'; - private readonly chatEditingSessionDisposables = new DisposableStore(); + private readonly chatEditingSessionDisposables = new Map(); private _currentRelatedFilesRetrievalOperation: Promise | undefined; constructor( @@ -26,35 +26,29 @@ export class ChatRelatedFilesContribution extends Disposable implements IWorkben ) { super(); - this._register(autorun(r => { - this.chatEditingSessionDisposables.clear(); - const session = this.chatEditingService.globalEditingSessionObs.read(r); - if (session) { - this._handleNewEditingSession(session); - } - })); + this._register( + this.chatWidgetService.onDidAddWidget(widget => { + if (widget.location === ChatAgentLocation.EditingSession && widget.viewModel?.sessionId) { + const editingSession = this.chatEditingService.getEditingSession(widget.viewModel.sessionId); + if (editingSession) { + this._handleNewEditingSession(editingSession, widget); + } + } + }), + ); } - private _updateRelatedFileSuggestions() { + private _updateRelatedFileSuggestions(currentEditingSession: IChatEditingSession, widget: IChatWidget) { if (this._currentRelatedFilesRetrievalOperation) { return; } - const currentEditingSession = this.chatEditingService.globalEditingSessionObs.get(); - if (!currentEditingSession) { - return; - } const workingSetEntries = currentEditingSession.entries.get(); if (workingSetEntries.length > 0) { // Do this only for the initial working set state return; } - const widget = this.chatWidgetService.getWidgetBySessionId(currentEditingSession.chatSessionId); - if (!widget) { - return; - } - this._currentRelatedFilesRetrievalOperation = this.chatEditingService.getRelatedFiles(currentEditingSession.chatSessionId, widget.getInput(), CancellationToken.None) .then((files) => { if (!files?.length) { @@ -98,29 +92,31 @@ export class ChatRelatedFilesContribution extends Disposable implements IWorkben } - private _handleNewEditingSession(currentEditingSession: IChatEditingSession) { - - const widget = this.chatWidgetService.getWidgetBySessionId(currentEditingSession.chatSessionId); - if (!widget || widget.viewModel?.sessionId !== currentEditingSession.chatSessionId) { - return; - } - this.chatEditingSessionDisposables.add(currentEditingSession.onDidDispose(() => { - this.chatEditingSessionDisposables.clear(); + private _handleNewEditingSession(currentEditingSession: IChatEditingSession, widget: IChatWidget) { + const disposableStore = new DisposableStore(); + disposableStore.add(currentEditingSession.onDidDispose(() => { + disposableStore.clear(); })); - this._updateRelatedFileSuggestions(); + this._updateRelatedFileSuggestions(currentEditingSession, widget); const onDebouncedType = Event.debounce(widget.inputEditor.onDidChangeModelContent, () => null, 3000); - this.chatEditingSessionDisposables.add(onDebouncedType(() => { - this._updateRelatedFileSuggestions(); + disposableStore.add(onDebouncedType(() => { + this._updateRelatedFileSuggestions(currentEditingSession, widget); })); - this.chatEditingSessionDisposables.add(currentEditingSession.onDidChange((e) => { + disposableStore.add(currentEditingSession.onDidChange((e) => { if (e === ChatEditingSessionChangeType.WorkingSet) { - this._updateRelatedFileSuggestions(); + this._updateRelatedFileSuggestions(currentEditingSession, widget); } })); + disposableStore.add(currentEditingSession.onDidDispose(() => { + disposableStore.dispose(); + })); + this.chatEditingSessionDisposables.set(currentEditingSession.chatSessionId, disposableStore); } override dispose() { - this.chatEditingSessionDisposables.dispose(); + for (const store of this.chatEditingSessionDisposables.values()) { + store.dispose(); + } super.dispose(); } } From 0185e3b037e035edb89f976babc6ec40a2d337ac Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 5 Feb 2025 11:45:24 +0100 Subject: [PATCH 0380/2632] Flip the zone widget noMax to be useMax (opt in) (#239672) Per the candidate fix in #239589 Fixes #239578 --- src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts | 4 ++-- .../contrib/comments/browser/commentThreadZoneWidget.ts | 2 +- src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts index 82c4eb90f567..611fcb7e21c1 100644 --- a/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/browser/zoneWidget.ts @@ -497,9 +497,9 @@ export abstract class ZoneWidget implements IHorizontalSashLayoutProvider { // implement in subclass } - protected _relayout(_newHeightInLines: number, noMax?: boolean): void { + protected _relayout(_newHeightInLines: number, useMax?: boolean): void { const maxHeightInLines = this._getMaximumHeightInLines(); - const newHeightInLines = (!noMax && (maxHeightInLines !== undefined)) ? Math.min(maxHeightInLines, _newHeightInLines) : _newHeightInLines; + const newHeightInLines = (useMax && (maxHeightInLines !== undefined)) ? Math.min(maxHeightInLines, _newHeightInLines) : _newHeightInLines; if (this._viewZone && this._viewZone.heightInLines !== newHeightInLines) { this.editor.changeViewZones(accessor => { if (this._viewZone) { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index d6c4d858d5c7..0039a4030eaa 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -502,7 +502,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } const capture = StableEditorScrollState.capture(this.editor); - this._relayout(computedLinesNumber, true); + this._relayout(computedLinesNumber); capture.restore(this.editor); } } diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 21417a0ea2e0..08611d53fc3e 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -794,7 +794,7 @@ class TestResultsPeek extends PeekViewWidget { const displayed = this._getMaximumHeightInLines(); if (displayed) { - this._relayout(Math.min(displayed, this.getVisibleEditorLines() / 2)); + this._relayout(Math.min(displayed, this.getVisibleEditorLines() / 2), true); if (!contentHeightSettleTimer.isScheduled()) { contentHeightSettleTimer.schedule(); } From 057edbab16852247e3df1b176ed2d20f66f754fb Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 5 Feb 2025 12:26:25 +0100 Subject: [PATCH 0381/2632] add sync vs async test case for error'ing `provideLanguageModelResponse` calls (#239676) https://github.com/microsoft/vscode/issues/235322 --- .../src/singlefolder-tests/lm.test.ts | 36 ++++++++++++--- .../api/browser/mainThreadLanguageModels.ts | 8 +++- .../api/common/extHostLanguageModels.ts | 44 ++++++++++--------- 3 files changed, 61 insertions(+), 27 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts index cc995c08497b..97875753f88c 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts @@ -139,11 +139,34 @@ suite('lm', function () { } }); - test('LanguageModelError instance is not thrown to extensions#235322', async function () { + test('LanguageModelError instance is not thrown to extensions#235322 (SYNC)', async function () { + + disposables.push(vscode.lm.registerChatModelProvider('test-lm', { + provideLanguageModelResponse(_messages, _options, _extensionId, _progress, _token) { + throw vscode.LanguageModelError.Blocked('You have been blocked SYNC'); + }, + async provideTokenCount(_text, _token) { + return 1; + } + }, testProviderOptions)); + + const models = await vscode.lm.selectChatModels({ id: 'test-lm' }); + assert.strictEqual(models.length, 1); + + try { + await models[0].sendRequest([vscode.LanguageModelChatMessage.User('Hello')]); + assert.ok(false, 'EXPECTED error'); + } catch (error) { + assert.ok(error instanceof vscode.LanguageModelError); + assert.strictEqual(error.message, 'You have been blocked SYNC'); + } + }); + + test('LanguageModelError instance is not thrown to extensions#235322 (ASYNC)', async function () { disposables.push(vscode.lm.registerChatModelProvider('test-lm', { async provideLanguageModelResponse(_messages, _options, _extensionId, _progress, _token) { - throw vscode.LanguageModelError.Blocked('You have been blocked'); + throw vscode.LanguageModelError.Blocked('You have been blocked ASYNC'); }, async provideTokenCount(_text, _token) { return 1; @@ -153,17 +176,18 @@ suite('lm', function () { const models = await vscode.lm.selectChatModels({ id: 'test-lm' }); assert.strictEqual(models.length, 1); - let output = ''; + const response = await models[0].sendRequest([vscode.LanguageModelChatMessage.User('Hello')]); + assert.ok(response); + + let output = ''; try { - const response = await models[0].sendRequest([vscode.LanguageModelChatMessage.User('Hello')]); - assert.ok(response); for await (const thing of response.text) { output += thing; } } catch (error) { assert.ok(error instanceof vscode.LanguageModelError); - assert.strictEqual(error.message, 'You have been blocked'); + assert.strictEqual(error.message, 'You have been blocked ASYNC'); } assert.strictEqual(output, ''); }); diff --git a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts index 1b1cb20d72b6..693eab881f2f 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageModels.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageModels.ts @@ -123,7 +123,13 @@ export class MainThreadLanguageModels implements MainThreadLanguageModelsShape { async $tryStartChatRequest(extension: ExtensionIdentifier, providerId: string, requestId: number, messages: IChatMessage[], options: {}, token: CancellationToken): Promise { this._logService.trace('[CHAT] request STARTED', extension.value, requestId); - const response = await this._chatProviderService.sendChatRequest(providerId, extension, messages, options, token); + let response: ILanguageModelChatResponse; + try { + response = await this._chatProviderService.sendChatRequest(providerId, extension, messages, options, token); + } catch (err) { + this._logService.error('[CHAT] request FAILED', extension.value, requestId, err); + throw err; + } // !!! IMPORTANT !!! // This method must return before the response is done (has streamed all parts) diff --git a/src/vs/workbench/api/common/extHostLanguageModels.ts b/src/vs/workbench/api/common/extHostLanguageModels.ts index 7ae740ff9fa5..ff74aa62a75f 100644 --- a/src/vs/workbench/api/common/extHostLanguageModels.ts +++ b/src/vs/workbench/api/common/extHostLanguageModels.ts @@ -220,30 +220,34 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { this._proxy.$reportResponsePart(requestId, { index: fragment.index, part }); }); - let p: Promise; + let value: any; - if (data.provider.provideLanguageModelResponse2) { - - p = Promise.resolve(data.provider.provideLanguageModelResponse2( - messages.map(typeConvert.LanguageModelChatMessage.to), - options, - ExtensionIdentifier.toKey(from), - progress, - token - )); + try { + if (data.provider.provideLanguageModelResponse2) { + value = data.provider.provideLanguageModelResponse2( + messages.map(typeConvert.LanguageModelChatMessage.to), + options, + ExtensionIdentifier.toKey(from), + progress, + token + ); - } else { + } else { + value = data.provider.provideLanguageModelResponse( + messages.map(typeConvert.LanguageModelChatMessage.to), + options, + ExtensionIdentifier.toKey(from), + progress, + token + ); + } - p = Promise.resolve(data.provider.provideLanguageModelResponse( - messages.map(typeConvert.LanguageModelChatMessage.to), - options, - ExtensionIdentifier.toKey(from), - progress, - token - )); + } catch (err) { + // synchronously failed + throw err; } - p.then(() => { + Promise.resolve(value).then(() => { this._proxy.$reportResponseDone(requestId, undefined); }, err => { this._proxy.$reportResponseDone(requestId, transformErrorForSerialization(err)); @@ -391,7 +395,7 @@ export class ExtHostLanguageModels implements ExtHostLanguageModelsShape { // error'ing here means that the request could NOT be started/made, e.g. wrong model, no access, etc, but // later the response can fail as well. Those failures are communicated via the stream-object this._pendingRequest.delete(requestId); - throw error; + throw extHostTypes.LanguageModelError.tryDeserialize(error) ?? error; } return res.apiObject; From 7e63e808c98af6078a7f3f78e38503174f71c076 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 5 Feb 2025 13:02:09 +0100 Subject: [PATCH 0382/2632] Fix some disposable leak warnings --- src/vs/workbench/browser/parts/titlebar/menubarControl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 50e683186508..48de66e71071 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -646,7 +646,7 @@ export class CustomMenubarControl extends MenubarControl { title = menuItem.item.toggled.mnemonicTitle ?? menuItem.item.toggled.title ?? title; } - const newAction = new Action(menuItem.id, mnemonicMenuLabel(title), menuItem.class, menuItem.enabled, () => this.commandService.executeCommand(menuItem.id)); + const newAction = this._register(new Action(menuItem.id, mnemonicMenuLabel(title), menuItem.class, menuItem.enabled, () => this.commandService.executeCommand(menuItem.id))); newAction.tooltip = menuItem.tooltip; newAction.checked = menuItem.checked; target.push(newAction); From f7db3121e476b873840c44f0a10c4245a22d9cbb Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 5 Feb 2025 14:08:18 +0100 Subject: [PATCH 0383/2632] debt: do not pass window loggers (#239680) --- src/vs/platform/log/electron-main/loggerService.ts | 6 +++--- .../sharedProcess/electron-main/sharedProcess.ts | 2 +- src/vs/platform/window/common/window.ts | 5 +---- src/vs/platform/windows/electron-main/windowImpl.ts | 5 +---- .../windows/electron-main/windowsMainService.ts | 10 ++-------- src/vs/workbench/electron-sandbox/desktop.main.ts | 5 +---- .../electron-sandbox/workingCopyBackupService.test.ts | 2 +- 7 files changed, 10 insertions(+), 25 deletions(-) diff --git a/src/vs/platform/log/electron-main/loggerService.ts b/src/vs/platform/log/electron-main/loggerService.ts index 5d569519f79a..0291c12e3e1a 100644 --- a/src/vs/platform/log/electron-main/loggerService.ts +++ b/src/vs/platform/log/electron-main/loggerService.ts @@ -26,7 +26,7 @@ export interface ILoggerMainService extends ILoggerService { registerLogger(resource: ILoggerResource, windowId?: number): void; - getRegisteredLoggers(windowId?: number): ILoggerResource[]; + getGlobalLoggers(): ILoggerResource[]; deregisterLoggers(windowId: number): void; @@ -60,10 +60,10 @@ export class LoggerMainService extends LoggerService implements ILoggerMainServi super.deregisterLogger(resource); } - override getRegisteredLoggers(windowId?: number): ILoggerResource[] { + getGlobalLoggers(): ILoggerResource[] { const resources: ILoggerResource[] = []; for (const resource of super.getRegisteredLoggers()) { - if (windowId === this.loggerResourcesByWindow.get(resource.resource)) { + if (!this.loggerResourcesByWindow.has(resource.resource)) { resources.push(resource); } } diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index 054f3b7419dc..9e7389c9826b 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -190,7 +190,7 @@ export class SharedProcess extends Disposable { }, args: this.environmentMainService.args, logLevel: this.loggerMainService.getLogLevel(), - loggers: this.loggerMainService.getRegisteredLoggers(), + loggers: this.loggerMainService.getGlobalLoggers(), policiesData: this.policyService.serialize() }; } diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts index cec025d66324..105fc2886b3a 100644 --- a/src/vs/platform/window/common/window.ts +++ b/src/vs/platform/window/common/window.ts @@ -392,10 +392,7 @@ export interface INativeWindowConfiguration extends IWindowConfiguration, Native isInitialStartup?: boolean; logLevel: LogLevel; - loggers: { - global: UriDto[]; - window: UriDto[]; - }; + loggers: UriDto[]; fullscreen?: boolean; maximized?: boolean; diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts index f5bf1b62cb9c..a3f0ad85d78a 100644 --- a/src/vs/platform/windows/electron-main/windowImpl.ts +++ b/src/vs/platform/windows/electron-main/windowImpl.ts @@ -1121,10 +1121,7 @@ export class CodeWindow extends BaseWindow implements ICodeWindow { home: this.userDataProfilesService.profilesHome }; configuration.logLevel = this.loggerMainService.getLogLevel(); - configuration.loggers = { - window: this.loggerMainService.getRegisteredLoggers(this.id), - global: this.loggerMainService.getRegisteredLoggers() - }; + configuration.loggers = this.loggerMainService.getGlobalLoggers(); // Load config this.load(configuration, { isReload: true, disableExtensions: cli?.['disable-extensions'] }); diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index d569ef654779..ec20394532be 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -1496,10 +1496,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic filesToWait: options.filesToOpen?.filesToWait, logLevel: this.loggerService.getLogLevel(), - loggers: { - window: [], - global: this.loggerService.getRegisteredLoggers() - }, + loggers: this.loggerService.getGlobalLoggers(), logsPath: this.environmentMainService.logsHome.with({ scheme: Schemas.file }).fsPath, product, @@ -1583,10 +1580,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic configuration['extensions-dir'] = currentWindowConfig['extensions-dir']; configuration['disable-extensions'] = currentWindowConfig['disable-extensions']; } - configuration.loggers = { - global: configuration.loggers.global, - window: currentWindowConfig?.loggers.window ?? configuration.loggers.window - }; + configuration.loggers = configuration.loggers; } // Update window identifier and session now diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 04b054017cf0..a728f602f530 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -205,10 +205,7 @@ export class DesktopMain extends Disposable { serviceCollection.set(INativeWorkbenchEnvironmentService, environmentService); // Logger - const loggers = [ - ...this.configuration.loggers.global.map(loggerResource => ({ ...loggerResource, resource: URI.revive(loggerResource.resource) })), - ...this.configuration.loggers.window.map(loggerResource => ({ ...loggerResource, resource: URI.revive(loggerResource.resource), hidden: true })), - ]; + const loggers = this.configuration.loggers.map(loggerResource => ({ ...loggerResource, resource: URI.revive(loggerResource.resource) })); const loggerService = new LoggerChannelClient(this.configuration.windowId, this.configuration.logLevel, environmentService.windowLogsPath, loggers, mainProcessService.getChannel('logger')); serviceCollection.set(ILoggerService, loggerService); diff --git a/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupService.test.ts b/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupService.test.ts index a6013924b1ba..ca4531327275 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupService.test.ts @@ -58,7 +58,7 @@ const TestNativeWindowConfiguration: INativeWindowConfiguration = { sqmId: 'testSqmId', devDeviceId: 'testdevDeviceId', logLevel: LogLevel.Error, - loggers: { global: [], window: [] }, + loggers: [], mainPid: 0, appRoot: '', userEnv: {}, From bf3a0f0e89f1658cd63afadf56dc3b419ac810ab Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 5 Feb 2025 05:18:03 -0800 Subject: [PATCH 0384/2632] rm fig spec tests, allow multiple template requests Fixes #239606 --- .../src/terminalSuggestMain.ts | 50 ++++++++++++------- .../src/test/completions/code.test.ts | 8 +-- .../src/test/completions/upstream/ls.test.ts | 26 +++++----- .../src/test/completions/upstream/rm.test.ts | 40 +++++++++++++++ .../src/test/terminalSuggestMain.test.ts | 2 + 5 files changed, 90 insertions(+), 36 deletions(-) create mode 100644 extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 7c375d4ba706..fa79d5c378aa 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -240,6 +240,8 @@ export async function getCompletionItemsFromSpecs( let foldersRequested = false; const precedingText = terminalContext.commandLine.slice(0, terminalContext.cursorPosition + 1); + // TODO: Normalize precedingText to ignore file extensions on Windows + // precedingText = precedingText.replace('.cmd', ''); let specificItemsProvided = false; for (const spec of specs) { @@ -251,6 +253,8 @@ export async function getCompletionItemsFromSpecs( for (const specLabel of specLabels) { const availableCommand = availableCommands.find(command => specLabel === command.label); + // TODO: Normalize commands to ignore file extensions on Windows https://github.com/microsoft/vscode/issues/237598 + // const availableCommand = availableCommands.find(command => command.label.startsWith(specLabel)); if (!availableCommand || (token && token.isCancellationRequested)) { continue; } @@ -263,27 +267,29 @@ export async function getCompletionItemsFromSpecs( continue; } + // TODO: Normalize commands to ignore file extensions on Windows https://github.com/microsoft/vscode/issues/237598 + // const commandAndAliases = availableCommands.filter(command => specLabel === (command.definitionCommand ?? command.label).replace('.cmd', '')); + // if (!commandAndAliases.some(e => terminalContext.commandLine.startsWith(`${e.label} `) || terminalContext.commandLine.startsWith(`${e.label}.cmd `))) { const commandAndAliases = availableCommands.filter(command => specLabel === (command.definitionCommand ?? command.label)); if (!commandAndAliases.some(e => terminalContext.commandLine.startsWith(`${e.label} `))) { // the spec label is not the first word in the command line, so do not provide options or args continue; } - const argsCompletionResult = handleArguments(specLabel, spec, terminalContext, precedingText); - if (argsCompletionResult) { - items.push(...argsCompletionResult.items); - filesRequested ||= argsCompletionResult.filesRequested; - foldersRequested ||= argsCompletionResult.foldersRequested; - specificItemsProvided ||= argsCompletionResult.items.length > 0; + const optionsCompletionResult = handleOptions(specLabel, spec, terminalContext, precedingText, prefix); + if (optionsCompletionResult) { + items.push(...optionsCompletionResult.items); + filesRequested ||= optionsCompletionResult.filesRequested; + foldersRequested ||= optionsCompletionResult.foldersRequested; + specificItemsProvided ||= optionsCompletionResult.items.length > 0; } - if (!argsCompletionResult?.items.length) { - // Arg completions are more specific, only get options if those are not provided. - const optionsCompletionResult = handleOptions(specLabel, spec, terminalContext, precedingText, prefix); - if (optionsCompletionResult) { - items.push(...optionsCompletionResult.items); - filesRequested ||= optionsCompletionResult.filesRequested; - foldersRequested ||= optionsCompletionResult.foldersRequested; - specificItemsProvided ||= optionsCompletionResult.items.length > 0; + if (!optionsCompletionResult?.isOptionArg) { + const argsCompletionResult = handleArguments(specLabel, spec, terminalContext, precedingText); + if (argsCompletionResult) { + items.push(...argsCompletionResult.items); + filesRequested ||= argsCompletionResult.filesRequested; + foldersRequested ||= argsCompletionResult.foldersRequested; + specificItemsProvided ||= argsCompletionResult.items.length > 0; } } } @@ -341,7 +347,7 @@ function handleArguments(specLabel: string, spec: Fig.Spec, terminalContext: { c return argsCompletions; } -function handleOptions(specLabel: string, spec: Fig.Spec, terminalContext: { commandLine: string; cursorPosition: number }, precedingText: string, prefix: string): { items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean } | undefined { +function handleOptions(specLabel: string, spec: Fig.Spec, terminalContext: { commandLine: string; cursorPosition: number }, precedingText: string, prefix: string): { items: vscode.TerminalCompletionItem[]; filesRequested: boolean; foldersRequested: boolean; isOptionArg: boolean } | undefined { let options; if ('options' in spec && spec.options) { options = spec.options; @@ -387,12 +393,12 @@ function handleOptions(specLabel: string, spec: Fig.Spec, terminalContext: { com const argsCompletions = getCompletionItemsFromArgs(option.args, currentPrefix, terminalContext); if (argsCompletions) { - return { items: argsCompletions.items, filesRequested: argsCompletions.filesRequested, foldersRequested: argsCompletions.foldersRequested }; + return { items: argsCompletions.items, filesRequested: argsCompletions.filesRequested, foldersRequested: argsCompletions.foldersRequested, isOptionArg: true }; } } } - return { items: optionItems, filesRequested: false, foldersRequested: false }; + return { items: optionItems, filesRequested: false, foldersRequested: false, isOptionArg: false }; } @@ -409,11 +415,17 @@ function getCompletionItemsFromArgs(args: Fig.SingleOrArray | undefined continue; } if (arg.template) { - if (arg.template === 'filepaths') { + if (Array.isArray(arg.template) ? arg.template.includes('filepaths') : arg.template === 'filepaths') { filesRequested = true; - } else if (arg.template === 'folders') { + } + if (Array.isArray(arg.template) ? arg.template.includes('folders') : arg.template === 'folders') { foldersRequested = true; } + // if (arg.template === 'filepaths') { + // filesRequested = true; + // } else if (arg.template === 'folders') { + // foldersRequested = true; + // } } if (arg.suggestions?.length) { // there are specific suggestions to show diff --git a/extensions/terminal-suggest/src/test/completions/code.test.ts b/extensions/terminal-suggest/src/test/completions/code.test.ts index f65cd2e4d5d6..50ad0458a224 100644 --- a/extensions/terminal-suggest/src/test/completions/code.test.ts +++ b/extensions/terminal-suggest/src/test/completions/code.test.ts @@ -25,7 +25,7 @@ export function createCodeTestSpecs(executable: string): ITestSpec[] { ...typingTests, // Basic arguments - { input: `${executable} |`, expectedCompletions: codeOptions }, + { input: `${executable} |`, expectedCompletions: codeOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, { input: `${executable} --locale |`, expectedCompletions: localeOptions }, { input: `${executable} --diff |`, expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } }, { input: `${executable} --diff ./file1 |`, expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } }, @@ -40,13 +40,13 @@ export function createCodeTestSpecs(executable: string): ITestSpec[] { { input: `${executable} --log |`, expectedCompletions: logOptions }, { input: `${executable} --sync |`, expectedCompletions: syncOptions }, { input: `${executable} --extensions-dir |`, expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, - { input: `${executable} --list-extensions |`, expectedCompletions: codeOptions }, - { input: `${executable} --show-versions |`, expectedCompletions: codeOptions }, + { input: `${executable} --list-extensions |`, expectedCompletions: codeOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: `${executable} --show-versions |`, expectedCompletions: codeOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, { input: `${executable} --category |`, expectedCompletions: categoryOptions }, { input: `${executable} --category a|`, expectedCompletions: categoryOptions.filter(c => c.startsWith('a')) }, // Middle of command - { input: `${executable} | --locale`, expectedCompletions: codeOptions }, + { input: `${executable} | --locale`, expectedCompletions: codeOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, ]; } diff --git a/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts index dfd7da8a93e9..36ea72e40835 100644 --- a/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts +++ b/extensions/terminal-suggest/src/test/completions/upstream/ls.test.ts @@ -67,27 +67,27 @@ export const lsTestSuiteSpec: ISuiteSpec = { // Basic options // TODO: The spec wants file paths and folders (which seems like it should only be folders), // but neither are requested https://github.com/microsoft/vscode/issues/239606 - { input: 'ls |', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } - { input: 'ls -|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + { input: 'ls |', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ls -|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, // Filtering options should request all options so client side can filter - { input: 'ls -a|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + { input: 'ls -a|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, // Duplicate option // TODO: Duplicate options should not be presented https://github.com/microsoft/vscode/issues/239607 - // { input: 'ls -a -|', expectedCompletions: removeArrayEntry(allOptions, '-a'), expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + // { input: 'ls -a -|', expectedCompletions: removeArrayEntry(allOptions, '-a'), expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, // Relative paths - { input: 'ls c|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } - { input: 'ls child|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } - { input: 'ls .|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } - { input: 'ls ./|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } - { input: 'ls ./child|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } - { input: 'ls ..|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwd } + { input: 'ls c|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ls child|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ls .|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ls ./|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ls ./child|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'ls ..|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, // Relative directories (changes cwd due to /) - { input: 'ls child/|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwdChild } - { input: 'ls ../|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwdParent } - { input: 'ls ../sibling|', expectedCompletions: allOptions, expectedResourceRequests: undefined }, // { type: 'folders', cwd: testPaths.cwdParent } + { input: 'ls child/|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwdChild } }, + { input: 'ls ../|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwdParent } }, + { input: 'ls ../sibling|', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwdParent } }, ] }; diff --git a/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts new file mode 100644 index 000000000000..ad025c27a7b2 --- /dev/null +++ b/extensions/terminal-suggest/src/test/completions/upstream/rm.test.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import { testPaths, type ISuiteSpec } from '../../helpers'; +import rmSpec from '../../../completions/upstream/rm'; + +const allOptions = [ + '-P', + '-R', + '-d', + '-f', + '-i', + '-r', + '-v', +]; + +export const rmTestSuiteSpec: ISuiteSpec = { + name: 'rm', + completionSpecs: rmSpec, + availableCommands: 'rm', + testSpecs: [ + // Empty input + { input: '|', expectedCompletions: ['rm'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Typing the command + { input: 'r|', expectedCompletions: ['rm'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'rm|', expectedCompletions: ['rm'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Basic options + { input: 'rm |', expectedCompletions: allOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Duplicate option + // TODO: Duplicate options should not be presented https://github.com/microsoft/vscode/issues/239607 + // { input: `rm -${allOptions[0]} -|`, expectedCompletions: removeArrayEntries(allOptions, allOptions[0]) }, + // { input: `rm -${allOptions[0]} -${allOptions[1]} -|`, expectedCompletions: removeArrayEntries(allOptions, allOptions[0], allOptions[1]) }, + ] +}; diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index a79f1c7432af..0cdf23b70d9a 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -15,6 +15,7 @@ import { codeInsidersTestSuite } from './completions/code-insiders.test'; import { lsTestSuiteSpec } from './completions/upstream/ls.test'; import { echoTestSuiteSpec } from './completions/upstream/echo.test'; import { mkdirTestSuiteSpec } from './completions/upstream/mkdir.test'; +import { rmTestSuiteSpec } from './completions/upstream/rm.test'; const testSpecs2: ISuiteSpec[] = [ { @@ -38,6 +39,7 @@ const testSpecs2: ISuiteSpec[] = [ echoTestSuiteSpec, lsTestSuiteSpec, mkdirTestSuiteSpec, + rmTestSuiteSpec, ]; suite('Terminal Suggest', () => { From b1cf1fd15101d61d795decfca87b3e9cf527cfdf Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 5 Feb 2025 05:23:14 -0800 Subject: [PATCH 0385/2632] rmdir and touch fig spec tests --- .../src/terminalSuggestMain.ts | 5 --- .../test/completions/upstream/rmdir.test.ts | 29 +++++++++++++++ .../test/completions/upstream/touch.test.ts | 36 +++++++++++++++++++ .../src/test/terminalSuggestMain.test.ts | 4 +++ 4 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts create mode 100644 extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index fa79d5c378aa..685ed8410056 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -421,11 +421,6 @@ function getCompletionItemsFromArgs(args: Fig.SingleOrArray | undefined if (Array.isArray(arg.template) ? arg.template.includes('folders') : arg.template === 'folders') { foldersRequested = true; } - // if (arg.template === 'filepaths') { - // filesRequested = true; - // } else if (arg.template === 'folders') { - // foldersRequested = true; - // } } if (arg.suggestions?.length) { // there are specific suggestions to show diff --git a/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts new file mode 100644 index 000000000000..e77092edb68b --- /dev/null +++ b/extensions/terminal-suggest/src/test/completions/upstream/rmdir.test.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import { testPaths, type ISuiteSpec } from '../../helpers'; +import rmdirSpec from '../../../completions/upstream/rmdir'; + +const allOptions = [ + '-p', +]; + +export const rmdirTestSuiteSpec: ISuiteSpec = { + name: 'rmdir', + completionSpecs: rmdirSpec, + availableCommands: 'rmdir', + testSpecs: [ + // Empty input + { input: '|', expectedCompletions: ['rmdir'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Typing the command + { input: 'r|', expectedCompletions: ['rmdir'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'rmdir|', expectedCompletions: ['rmdir'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Basic options + { input: 'rmdir |', expectedCompletions: allOptions, expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + ] +}; diff --git a/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts b/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts new file mode 100644 index 000000000000..da70835a5932 --- /dev/null +++ b/extensions/terminal-suggest/src/test/completions/upstream/touch.test.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import { testPaths, type ISuiteSpec } from '../../helpers'; +import touchSpec from '../../../completions/upstream/touch'; + +const allOptions = [ + '-A', + '-a', + '-c', + '-f', + '-h', + '-m', + '-r', + '-t', +]; + +export const touchTestSuiteSpec: ISuiteSpec = { + name: 'touch', + completionSpecs: touchSpec, + availableCommands: 'touch', + testSpecs: [ + // Empty input + { input: '|', expectedCompletions: ['touch'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Typing the command + { input: 't|', expectedCompletions: ['touch'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'touch|', expectedCompletions: ['touch'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Basic options + { input: 'touch |', expectedCompletions: allOptions, expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + ] +}; diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index 0cdf23b70d9a..eca864c9727c 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -16,6 +16,8 @@ import { lsTestSuiteSpec } from './completions/upstream/ls.test'; import { echoTestSuiteSpec } from './completions/upstream/echo.test'; import { mkdirTestSuiteSpec } from './completions/upstream/mkdir.test'; import { rmTestSuiteSpec } from './completions/upstream/rm.test'; +import { rmdirTestSuiteSpec } from './completions/upstream/rmdir.test'; +import { touchTestSuiteSpec } from './completions/upstream/touch.test'; const testSpecs2: ISuiteSpec[] = [ { @@ -40,6 +42,8 @@ const testSpecs2: ISuiteSpec[] = [ lsTestSuiteSpec, mkdirTestSuiteSpec, rmTestSuiteSpec, + rmdirTestSuiteSpec, + touchTestSuiteSpec, ]; suite('Terminal Suggest', () => { From fb31b5b05f7f980fd1e36dc8adc35947c065ced7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 5 Feb 2025 05:41:22 -0800 Subject: [PATCH 0386/2632] Don't treat dotfiles with a single period as having an extension --- .../services/suggest/browser/simpleCompletionItem.ts | 3 ++- .../suggest/test/browser/simpleCompletionModel.test.ts | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts b/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts index b1301167f1a0..d3590fbf873c 100644 --- a/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts +++ b/src/vs/workbench/services/suggest/browser/simpleCompletionItem.ts @@ -101,8 +101,9 @@ export class SimpleCompletionItem { if (isWindows) { this.labelLow = this.labelLow.replaceAll('/', '\\'); } + // Don't include dotfiles as extensions when sorting const extIndex = this.labelLow.lastIndexOf('.'); - if (extIndex !== -1) { + if (extIndex > 0) { this.labelLowExcludeFileExt = this.labelLow.substring(0, extIndex); this.fileExtLow = this.labelLow.substring(extIndex + 1); } diff --git a/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts b/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts index 706c1fc1f30b..216f8c91b78a 100644 --- a/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts +++ b/src/vs/workbench/services/suggest/test/browser/simpleCompletionModel.test.ts @@ -157,8 +157,6 @@ suite('SimpleCompletionModel', function () { '.configurations', 'CONTRIBUTING.md', '.devcontainer', - '.npmrc', - '.gitignore', '.editorconfig', 'eslint.config.js', '.eslint-ignore', @@ -167,13 +165,15 @@ suite('SimpleCompletionModel', function () { '.gitattributes', '.git-blame-ignore-revs', '.github', + '.gitignore', 'gulpfile.js', 'LICENSE.txt', '.lsifrc.json', - '.nvmrc', '.mailmap', '.mention-bot', 'node_modules', + '.npmrc', + '.nvmrc', 'out', 'package.json', 'package-lock.json', From 18946eca523fd295e8971b37bc11cc479c087427 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 5 Feb 2025 14:45:23 +0100 Subject: [PATCH 0387/2632] skip integration test that needs latest insiders (#239681) --- extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts index 97875753f88c..ed00dc790426 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/lm.test.ts @@ -139,7 +139,8 @@ suite('lm', function () { } }); - test('LanguageModelError instance is not thrown to extensions#235322 (SYNC)', async function () { + // SKIPPED until Feb 6 2025 + test.skip('LanguageModelError instance is not thrown to extensions#235322 (SYNC)', async function () { disposables.push(vscode.lm.registerChatModelProvider('test-lm', { provideLanguageModelResponse(_messages, _options, _extensionId, _progress, _token) { From feb20a3698337c90f3017deee4e924754cade8ea Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 5 Feb 2025 15:11:51 +0100 Subject: [PATCH 0388/2632] migrate undo/redo actions (#239682) https://github.com/microsoft/vscode-copilot/issues/12820 --- .../chat/browser/actions/chatClearActions.ts | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index 52c81f2fbf39..2efaaef704b9 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -17,7 +17,7 @@ import { IViewsService } from '../../../../services/views/common/viewsService.js import { isChatViewTitleActionContext } from '../../common/chatActions.js'; import { ChatAgentLocation } from '../../common/chatAgents.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; -import { hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, IChatEditingSession, WorkingSetEntryState } from '../../common/chatEditingService.js'; +import { hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingSession, WorkingSetEntryState } from '../../common/chatEditingService.js'; import { ChatViewId, EditsViewId, IChatWidget, IChatWidgetService } from '../chat.js'; import { EditingSessionAction } from '../chatEditing/chatEditingActions.js'; import { ctxIsGlobalEditingSession } from '../chatEditing/chatEditingEditorController.js'; @@ -252,7 +252,7 @@ export function registerNewChatActions() { } }); - registerAction2(class UndoChatEditInteractionAction extends Action2 { + registerAction2(class UndoChatEditInteractionAction extends EditingSessionAction { constructor() { super({ id: 'workbench.action.chat.undoEdit', @@ -270,17 +270,12 @@ export function registerNewChatActions() { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { - const chatEditingService = accessor.get(IChatEditingService); - const currentEditingSession = chatEditingService.globalEditingSession; - if (!currentEditingSession) { - return; - } - await currentEditingSession.undoInteraction(); + async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession) { + await editingSession.undoInteraction(); } }); - registerAction2(class RedoChatEditInteractionAction extends Action2 { + registerAction2(class RedoChatEditInteractionAction extends EditingSessionAction { constructor() { super({ id: 'workbench.action.chat.redoEdit', @@ -298,13 +293,8 @@ export function registerNewChatActions() { }); } - async run(accessor: ServicesAccessor, ...args: any[]) { - const chatEditingService = accessor.get(IChatEditingService); - const currentEditingSession = chatEditingService.globalEditingSession; - if (!currentEditingSession) { - return; - } - await currentEditingSession.redoInteraction(); + async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession) { + await editingSession.redoInteraction(); } }); From ea87a7360231b204142eebd946dcc8a995673afe Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Wed, 5 Feb 2025 15:38:43 +0100 Subject: [PATCH 0389/2632] Better approximation of `fitsInsideViewport` for side by side (#239685) fixes https://github.com/microsoft/vscode-copilot/issues/12831 --- .../view/inlineEdits/sideBySideDiff.ts | 8 ++-- .../browser/view/inlineEdits/view.ts | 44 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts index cd78afcce01b..c94d6c4eaf35 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/sideBySideDiff.ts @@ -110,19 +110,19 @@ const ENABLE_OVERFLOW = false; export class InlineEditsSideBySideDiff extends Disposable implements IInlineEditsView { // This is an approximation and should be improved by using the real parameters used bellow - static fitsInsideViewport(editor: ICodeEditor, edit: InlineEditWithChanges, reader: IReader): boolean { + static fitsInsideViewport(editor: ICodeEditor, edit: InlineEditWithChanges, originalDisplayRange: LineRange, reader: IReader): boolean { const editorObs = observableCodeEditor(editor); const editorWidth = editorObs.layoutInfoWidth.read(reader); const editorContentLeft = editorObs.layoutInfoContentLeft.read(reader); - const editorVerticalScrollBar = editor.getLayoutInfo().verticalScrollbarWidth; + const editorVerticalScrollbar = editor.getLayoutInfo().verticalScrollbarWidth; const w = editor.getOption(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; - const maxOriginalContent = maxContentWidthInRange(editorObs, edit.originalLineRange, undefined/* do not reconsider on each layout info change */); + const maxOriginalContent = maxContentWidthInRange(editorObs, originalDisplayRange, undefined/* do not reconsider on each layout info change */); const maxModifiedContent = edit.lineEdit.newLines.reduce((max, line) => Math.max(max, line.length * w), 0); const endOfEditorPadding = 20; // padding after last line of editor const editorsPadding = edit.modifiedLineRange.length <= edit.originalLineRange.length ? PADDING * 3 + endOfEditorPadding : 60 + endOfEditorPadding * 2; - return maxOriginalContent + maxModifiedContent + editorsPadding < editorWidth - editorContentLeft - editorVerticalScrollBar; + return maxOriginalContent + maxModifiedContent + editorsPadding < editorWidth - editorContentLeft - editorVerticalScrollbar; } private readonly _editorObs = observableCodeEditor(this._editor); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts index d55b24fb5704..1ec946c6dec1 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/view.ts @@ -73,7 +73,13 @@ export class InlineEditsView extends Disposable { let newText = edit.edit.apply(edit.originalText); let diff = lineRangeMappingFromRangeMappings(mappings, edit.originalText, new StringText(newText)); - const state = this.determineRenderState(edit, reader, diff, new StringText(newText)); + const originalDisplayRange = edit.originalText.lineRange.intersect( + edit.originalLineRange.join( + LineRange.ofLength(edit.originalLineRange.startLineNumber, edit.lineEdit.newLines.length) + ) + )!; + + const state = this.determineRenderState(edit, reader, diff, new StringText(newText), originalDisplayRange); if (!state) { this._model.get()?.stop(); return undefined; @@ -87,12 +93,6 @@ export class InlineEditsView extends Disposable { diff = lineRangeMappingFromRangeMappings(mappings, edit.originalText, new StringText(newText)); } - const originalDisplayRange = edit.originalText.lineRange.intersect( - edit.originalLineRange.join( - LineRange.ofLength(edit.originalLineRange.startLineNumber, edit.lineEdit.newLines.length) - ) - )!; - this._previewTextModel.setLanguage(this._editor.getModel()!.getLanguageId()); const previousNewText = this._previewTextModel.getValue(); @@ -196,20 +196,21 @@ export class InlineEditsView extends Disposable { return 0; }); - private readonly _originalDisplayRange = derived(this, reader => { - const state = this._uiState.read(reader); - if (state?.state?.kind === 'insertionMultiLine') { - return this._insertion.originalLines.read(reader); - } - return state?.originalDisplayRange; - }); - protected readonly _indicator = this._register(autorunWithStore((reader, store) => { + + const indicatorDisplayRange = derived(this, reader => { + const state = this._uiState.read(reader); + if (state?.state?.kind === 'insertionMultiLine') { + return this._insertion.originalLines.read(reader); + } + return state?.originalDisplayRange; + }); + if (this._useGutterIndicator.read(reader)) { store.add(this._instantiationService.createInstance( InlineEditsGutterIndicator, this._editorObs, - this._originalDisplayRange, + indicatorDisplayRange, this._gutterIndicatorOffset, this._model, this._inlineEditsIsHovered, @@ -220,7 +221,7 @@ export class InlineEditsView extends Disposable { this._editorObs, derived(reader => { const state = this._uiState.read(reader); - const range = this._originalDisplayRange.read(reader); + const range = indicatorDisplayRange.read(reader); if (!state || !state.state || !range) { return undefined; } const top = this._editor.getTopForLineNumber(range.startLineNumber) - this._editorObs.scrollTop.read(reader) + this._gutterIndicatorOffset.read(reader); return { editTop: top, showAlways: state.state.kind !== 'sideBySide' }; @@ -230,7 +231,7 @@ export class InlineEditsView extends Disposable { } })); - private determineView(edit: InlineEditWithChanges, reader: IReader, diff: DetailedLineRangeMapping[], newText: StringText): string { + private determineView(edit: InlineEditWithChanges, reader: IReader, diff: DetailedLineRangeMapping[], newText: StringText, originalDisplayRange: LineRange): string { // Check if we can use the previous view if it is the same InlineCompletion as previously shown const canUseCache = this._previousView?.id === edit.inlineCompletion.id; const reconsiderViewAfterJump = edit.userJumpedToIt !== this._previousView?.userJumpedToIt && @@ -283,9 +284,8 @@ export class InlineEditsView extends Disposable { return 'wordReplacements'; } } - if (numOriginalLines > 0 && numModifiedLines > 0) { - if (this._renderSideBySide.read(reader) !== 'never' && InlineEditsSideBySideDiff.fitsInsideViewport(this._editor, edit, reader)) { + if (this._renderSideBySide.read(reader) !== 'never' && InlineEditsSideBySideDiff.fitsInsideViewport(this._editor, edit, originalDisplayRange, reader)) { return 'sideBySide'; } @@ -306,9 +306,9 @@ export class InlineEditsView extends Disposable { return 'sideBySide'; } - private determineRenderState(edit: InlineEditWithChanges, reader: IReader, diff: DetailedLineRangeMapping[], newText: StringText) { + private determineRenderState(edit: InlineEditWithChanges, reader: IReader, diff: DetailedLineRangeMapping[], newText: StringText, originalDisplayRange: LineRange) { - const view = this.determineView(edit, reader, diff, newText); + const view = this.determineView(edit, reader, diff, newText, originalDisplayRange); this._previousView = { id: edit.inlineCompletion.id, view, userJumpedToIt: edit.userJumpedToIt, editorWidth: this._editor.getLayoutInfo().width }; From 297a228fb8e551e004f7b2d18c188293cb5a93f4 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 5 Feb 2025 15:41:30 +0100 Subject: [PATCH 0390/2632] Git - refactor delete remote tag (#239684) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Git - refactor delete remote tag * 💄 fix parameter name * 💄 add missing parameter name --- extensions/git/src/commands.ts | 2 +- extensions/git/src/git.ts | 10 ++++++++-- extensions/git/src/operation.ts | 8 ++++---- extensions/git/src/repository.ts | 4 ++-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index b7a1a85bb4f7..497edcb9e8e4 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -178,7 +178,7 @@ class RemoteTagDeleteItem extends RefItem { async run(repository: Repository, remote: string): Promise { if (this.ref.name) { - await repository.deleteRemoteTag(remote, this.ref.name); + await repository.deleteRemoteRef(remote, this.ref.name); } } } diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index b6d5582b8fb6..2b32add17ce1 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1927,8 +1927,14 @@ export class Repository { await this.exec(args); } - async deleteRemoteTag(remoteName: string, tagName: string): Promise { - const args = ['push', '--delete', remoteName, tagName]; + async deleteRemoteRef(remoteName: string, refName: string, options?: { force?: boolean }): Promise { + const args = ['push', remoteName, '--delete']; + + if (options?.force) { + args.push('--force'); + } + + args.push(refName); await this.exec(args); } diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts index 65a932f77020..4946c70d9665 100644 --- a/extensions/git/src/operation.ts +++ b/extensions/git/src/operation.ts @@ -20,7 +20,7 @@ export const enum OperationKind { Config = 'Config', DeleteBranch = 'DeleteBranch', DeleteRef = 'DeleteRef', - DeleteRemoteTag = 'DeleteRemoteTag', + DeleteRemoteRef = 'DeleteRemoteRef', DeleteTag = 'DeleteTag', Diff = 'Diff', Fetch = 'Fetch', @@ -67,7 +67,7 @@ export const enum OperationKind { export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchOperation | CheckIgnoreOperation | CherryPickOperation | CheckoutOperation | CheckoutTrackingOperation | CleanOperation | CommitOperation | ConfigOperation | DeleteBranchOperation | - DeleteRefOperation | DeleteRemoteTagOperation | DeleteTagOperation | DiffOperation | FetchOperation | FindTrackingBranchesOperation | + DeleteRefOperation | DeleteRemoteRefOperation | DeleteTagOperation | DiffOperation | FetchOperation | FindTrackingBranchesOperation | GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetObjectFilesOperation | GetRefsOperation | GetRemoteRefsOperation | HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation | MergeContinueOperation | MoveOperation | PostCommitCommandOperation | PullOperation | PushOperation | RemoteOperation | @@ -89,7 +89,7 @@ export type CommitOperation = BaseOperation & { kind: OperationKind.Commit }; export type ConfigOperation = BaseOperation & { kind: OperationKind.Config }; export type DeleteBranchOperation = BaseOperation & { kind: OperationKind.DeleteBranch }; export type DeleteRefOperation = BaseOperation & { kind: OperationKind.DeleteRef }; -export type DeleteRemoteTagOperation = BaseOperation & { kind: OperationKind.DeleteRemoteTag }; +export type DeleteRemoteRefOperation = BaseOperation & { kind: OperationKind.DeleteRemoteRef }; export type DeleteTagOperation = BaseOperation & { kind: OperationKind.DeleteTag }; export type DiffOperation = BaseOperation & { kind: OperationKind.Diff }; export type FetchOperation = BaseOperation & { kind: OperationKind.Fetch }; @@ -147,7 +147,7 @@ export const Operation = { Config: (readOnly: boolean) => ({ kind: OperationKind.Config, blocking: false, readOnly, remote: false, retry: false, showProgress: false } as ConfigOperation), DeleteBranch: { kind: OperationKind.DeleteBranch, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteBranchOperation, DeleteRef: { kind: OperationKind.DeleteRef, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteRefOperation, - DeleteRemoteTag: { kind: OperationKind.DeleteRemoteTag, blocking: false, readOnly: false, remote: true, retry: false, showProgress: true } as DeleteRemoteTagOperation, + DeleteRemoteRef: { kind: OperationKind.DeleteRemoteRef, blocking: false, readOnly: false, remote: true, retry: false, showProgress: true } as DeleteRemoteRefOperation, DeleteTag: { kind: OperationKind.DeleteTag, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteTagOperation, Diff: { kind: OperationKind.Diff, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as DiffOperation, Fetch: (showProgress: boolean) => ({ kind: OperationKind.Fetch, blocking: false, readOnly: false, remote: true, retry: true, showProgress } as FetchOperation), diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 560477c586d5..f659d1089c58 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1667,8 +1667,8 @@ export class Repository implements Disposable { await this.run(Operation.DeleteTag, () => this.repository.deleteTag(name)); } - async deleteRemoteTag(remoteName: string, tagName: string): Promise { - await this.run(Operation.DeleteRemoteTag, () => this.repository.deleteRemoteTag(remoteName, tagName)); + async deleteRemoteRef(remoteName: string, refName: string, options?: { force?: boolean }): Promise { + await this.run(Operation.DeleteRemoteRef, () => this.repository.deleteRemoteRef(remoteName, refName, options)); } async checkout(treeish: string, opts?: { detached?: boolean; pullBeforeCheckout?: boolean }): Promise { From cb3bc7b639ac3077f247ea63141a5a03ee5fa98f Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 5 Feb 2025 15:49:50 +0100 Subject: [PATCH 0391/2632] more `EditingSessionAction` adoption (#239686) --- .../chat/browser/actions/chatExecuteActions.ts | 15 ++++++--------- .../browser/chatEditing/chatEditingActions.ts | 14 +++++--------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 4931801e3048..ca3e4ca57282 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -6,12 +6,12 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; import { URI } from '../../../../../base/common/uri.js'; +import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; import { localize, localize2 } from '../../../../../nls.js'; import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { ICommandService } from '../../../../../platform/commands/common/commands.js'; import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; -import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js'; import { IViewsService } from '../../../../services/views/common/viewsService.js'; import { ChatAgentLocation, IChatAgentService } from '../../common/chatAgents.js'; @@ -95,7 +95,8 @@ export interface IToggleAgentModeArgs { agentMode: boolean; } -export class ToggleAgentModeAction extends Action2 { +export class ToggleAgentModeAction extends EditingSessionAction { + static readonly ID = ToggleAgentModeActionId; constructor() { @@ -132,20 +133,16 @@ export class ToggleAgentModeAction extends Action2 { }); } - override async run(accessor: ServicesAccessor, ...args: any[]): Promise { + override async runEditingSessionAction(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]) { + const agentService = accessor.get(IChatAgentService); - const chatEditingService = accessor.get(IChatEditingService); const chatService = accessor.get(IChatService); const commandService = accessor.get(ICommandService); const dialogService = accessor.get(IDialogService); - const currentEditingSession = chatEditingService.globalEditingSession; - if (!currentEditingSession) { - return; - } const entries = currentEditingSession.entries.get(); if (entries.length > 0 && entries.some(entry => entry.state.get() === WorkingSetEntryState.Modified)) { - if (!await discardAllEditsWithConfirmation(accessor)) { + if (!await discardAllEditsWithConfirmation(accessor, currentEditingSession)) { // User cancelled return; } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index 6cf5b9ae1b1b..74c5d98582a6 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -314,7 +314,7 @@ export class ChatEditingAcceptAllAction extends EditingSessionAction { } registerAction2(ChatEditingAcceptAllAction); -export class ChatEditingDiscardAllAction extends Action2 { +export class ChatEditingDiscardAllAction extends EditingSessionAction { constructor() { super({ @@ -345,19 +345,15 @@ export class ChatEditingDiscardAllAction extends Action2 { }); } - async run(accessor: ServicesAccessor, ...args: any[]): Promise { - await discardAllEditsWithConfirmation(accessor); + override async runEditingSessionAction(accessor: ServicesAccessor, editingSession: IChatEditingSession, chatWidget: IChatWidget, ...args: any[]) { + await discardAllEditsWithConfirmation(accessor, editingSession); } } registerAction2(ChatEditingDiscardAllAction); -export async function discardAllEditsWithConfirmation(accessor: ServicesAccessor): Promise { - const chatEditingService = accessor.get(IChatEditingService); +export async function discardAllEditsWithConfirmation(accessor: ServicesAccessor, currentEditingSession: IChatEditingSession): Promise { + const dialogService = accessor.get(IDialogService); - const currentEditingSession = chatEditingService.globalEditingSession; - if (!currentEditingSession) { - return false; - } // Ask for confirmation if there are any edits const entries = currentEditingSession.entries.get(); From a563227a21c235f16a6daa33a1f2e9efae253d9c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 5 Feb 2025 07:23:03 -0800 Subject: [PATCH 0392/2632] Use fig on windows regardless of file extension Fixes #237598 --- .../src/terminalSuggestMain.ts | 34 +++++++++++++------ .../src/test/completions/code.test.ts | 11 +++--- .../src/test/terminalSuggestMain.test.ts | 24 ++++++++++++- 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/extensions/terminal-suggest/src/terminalSuggestMain.ts b/extensions/terminal-suggest/src/terminalSuggestMain.ts index 685ed8410056..71bcfba77ca8 100644 --- a/extensions/terminal-suggest/src/terminalSuggestMain.ts +++ b/extensions/terminal-suggest/src/terminalSuggestMain.ts @@ -239,9 +239,15 @@ export async function getCompletionItemsFromSpecs( let filesRequested = false; let foldersRequested = false; - const precedingText = terminalContext.commandLine.slice(0, terminalContext.cursorPosition + 1); - // TODO: Normalize precedingText to ignore file extensions on Windows - // precedingText = precedingText.replace('.cmd', ''); + let precedingText = terminalContext.commandLine.slice(0, terminalContext.cursorPosition + 1); + if (isWindows) { + const spaceIndex = precedingText.indexOf(' '); + const commandEndIndex = spaceIndex === -1 ? precedingText.length : spaceIndex; + const lastDotIndex = precedingText.lastIndexOf('.', commandEndIndex); + if (lastDotIndex > 0) { // Don't treat dotfiles as extensions + precedingText = precedingText.substring(0, lastDotIndex) + precedingText.substring(spaceIndex); + } + } let specificItemsProvided = false; for (const spec of specs) { @@ -252,9 +258,9 @@ export async function getCompletionItemsFromSpecs( } for (const specLabel of specLabels) { - const availableCommand = availableCommands.find(command => specLabel === command.label); - // TODO: Normalize commands to ignore file extensions on Windows https://github.com/microsoft/vscode/issues/237598 - // const availableCommand = availableCommands.find(command => command.label.startsWith(specLabel)); + const availableCommand = (isWindows + ? availableCommands.find(command => command.label.match(new RegExp(`${specLabel}(\\.[^ ]+)?$`))) + : availableCommands.find(command => command.label.startsWith(specLabel))); if (!availableCommand || (token && token.isCancellationRequested)) { continue; } @@ -267,11 +273,14 @@ export async function getCompletionItemsFromSpecs( continue; } - // TODO: Normalize commands to ignore file extensions on Windows https://github.com/microsoft/vscode/issues/237598 - // const commandAndAliases = availableCommands.filter(command => specLabel === (command.definitionCommand ?? command.label).replace('.cmd', '')); - // if (!commandAndAliases.some(e => terminalContext.commandLine.startsWith(`${e.label} `) || terminalContext.commandLine.startsWith(`${e.label}.cmd `))) { - const commandAndAliases = availableCommands.filter(command => specLabel === (command.definitionCommand ?? command.label)); - if (!commandAndAliases.some(e => terminalContext.commandLine.startsWith(`${e.label} `))) { + const commandAndAliases = (isWindows + ? availableCommands.filter(command => specLabel === removeAnyFileExtension(command.definitionCommand ?? command.label)) + : availableCommands.filter(command => specLabel === (command.definitionCommand ?? command.label))); + if ( + !(isWindows + ? commandAndAliases.some(e => precedingText.startsWith(`${removeAnyFileExtension(e.label)} `)) + : commandAndAliases.some(e => precedingText.startsWith(`${e.label} `))) + ) { // the spec label is not the first word in the command line, so do not provide options or args continue; } @@ -469,3 +478,6 @@ function getShell(shellType: TerminalShellType): string | undefined { } } +function removeAnyFileExtension(label: string): string { + return label.replace(/\.[a-zA-Z0-9!#\$%&'\(\)\-@\^_`{}~\+,;=\[\]]+$/, ''); +} diff --git a/extensions/terminal-suggest/src/test/completions/code.test.ts b/extensions/terminal-suggest/src/test/completions/code.test.ts index 50ad0458a224..ecbaef0c130b 100644 --- a/extensions/terminal-suggest/src/test/completions/code.test.ts +++ b/extensions/terminal-suggest/src/test/completions/code.test.ts @@ -7,8 +7,9 @@ import 'mocha'; import codeCompletionSpec from '../../completions/code'; import { testPaths, type ISuiteSpec, type ITestSpec } from '../helpers'; +export const codeSpecOptions = ['-', '--add', '--category', '--diff', '--disable-extension', '--disable-extensions', '--disable-gpu', '--enable-proposed-api', '--extensions-dir', '--goto', '--help', '--inspect-brk-extensions', '--inspect-extensions', '--install-extension', '--list-extensions', '--locale', '--log', '--max-memory', '--merge', '--new-window', '--pre-release', '--prof-startup', '--profile', '--reuse-window', '--show-versions', '--status', '--sync', '--telemetry', '--uninstall-extension', '--user-data-dir', '--verbose', '--version', '--wait', '-a', '-d', '-g', '-h', '-m', '-n', '-r', '-s', '-v', '-w']; + export function createCodeTestSpecs(executable: string): ITestSpec[] { - const codeOptions = ['-', '--add', '--category', '--diff', '--disable-extension', '--disable-extensions', '--disable-gpu', '--enable-proposed-api', '--extensions-dir', '--goto', '--help', '--inspect-brk-extensions', '--inspect-extensions', '--install-extension', '--list-extensions', '--locale', '--log', '--max-memory', '--merge', '--new-window', '--pre-release', '--prof-startup', '--profile', '--reuse-window', '--show-versions', '--status', '--sync', '--telemetry', '--uninstall-extension', '--user-data-dir', '--verbose', '--version', '--wait', '-a', '-d', '-g', '-h', '-m', '-n', '-r', '-s', '-v', '-w']; const localeOptions = ['bg', 'de', 'en', 'es', 'fr', 'hu', 'it', 'ja', 'ko', 'pt-br', 'ru', 'tr', 'zh-CN', 'zh-TW']; const categoryOptions = ['azure', 'data science', 'debuggers', 'extension packs', 'education', 'formatters', 'keymaps', 'language packs', 'linters', 'machine learning', 'notebooks', 'programming languages', 'scm providers', 'snippets', 'testing', 'themes', 'visualization', 'other']; const logOptions = ['critical', 'error', 'warn', 'info', 'debug', 'trace', 'off']; @@ -25,7 +26,7 @@ export function createCodeTestSpecs(executable: string): ITestSpec[] { ...typingTests, // Basic arguments - { input: `${executable} |`, expectedCompletions: codeOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: `${executable} |`, expectedCompletions: codeSpecOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, { input: `${executable} --locale |`, expectedCompletions: localeOptions }, { input: `${executable} --diff |`, expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } }, { input: `${executable} --diff ./file1 |`, expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } }, @@ -40,13 +41,13 @@ export function createCodeTestSpecs(executable: string): ITestSpec[] { { input: `${executable} --log |`, expectedCompletions: logOptions }, { input: `${executable} --sync |`, expectedCompletions: syncOptions }, { input: `${executable} --extensions-dir |`, expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, - { input: `${executable} --list-extensions |`, expectedCompletions: codeOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, - { input: `${executable} --show-versions |`, expectedCompletions: codeOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: `${executable} --list-extensions |`, expectedCompletions: codeSpecOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: `${executable} --show-versions |`, expectedCompletions: codeSpecOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, { input: `${executable} --category |`, expectedCompletions: categoryOptions }, { input: `${executable} --category a|`, expectedCompletions: categoryOptions.filter(c => c.startsWith('a')) }, // Middle of command - { input: `${executable} | --locale`, expectedCompletions: codeOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: `${executable} | --locale`, expectedCompletions: codeSpecOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, ]; } diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index eca864c9727c..189a022101b7 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -9,7 +9,7 @@ import { basename } from 'path'; import { asArray, getCompletionItemsFromSpecs } from '../terminalSuggestMain'; import { getTokenType } from '../tokens'; import { cdTestSuiteSpec as cdTestSuite } from './completions/cd.test'; -import { codeTestSuite } from './completions/code.test'; +import { codeSpecOptions, codeTestSuite } from './completions/code.test'; import { testPaths, type ISuiteSpec } from './helpers'; import { codeInsidersTestSuite } from './completions/code-insiders.test'; import { lsTestSuiteSpec } from './completions/upstream/ls.test'; @@ -18,6 +18,8 @@ import { mkdirTestSuiteSpec } from './completions/upstream/mkdir.test'; import { rmTestSuiteSpec } from './completions/upstream/rm.test'; import { rmdirTestSuiteSpec } from './completions/upstream/rmdir.test'; import { touchTestSuiteSpec } from './completions/upstream/touch.test'; +import { osIsWindows } from '../helpers/os'; +import codeCompletionSpec from '../completions/code'; const testSpecs2: ISuiteSpec[] = [ { @@ -46,6 +48,26 @@ const testSpecs2: ISuiteSpec[] = [ touchTestSuiteSpec, ]; +if (osIsWindows()) { + testSpecs2.push({ + name: 'Handle options extensions on Windows', + completionSpecs: [codeCompletionSpec], + availableCommands: [ + 'code.bat', + 'code.cmd', + 'code.exe', + 'code.anything', + ], + testSpecs: [ + { input: 'code |', expectedCompletions: codeSpecOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'code.bat |', expectedCompletions: codeSpecOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'code.cmd |', expectedCompletions: codeSpecOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'code.exe |', expectedCompletions: codeSpecOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'code.anything |', expectedCompletions: codeSpecOptions, expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + ] + }); +} + suite('Terminal Suggest', () => { for (const suiteSpec of testSpecs2) { suite(suiteSpec.name, () => { From 47b33dc58d1577bae96d66488f2a7276d1322a83 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:32:11 +0100 Subject: [PATCH 0393/2632] Git - add command to delete remote branch (#239692) --- extensions/git/package.json | 14 +++++++++ extensions/git/package.nls.json | 1 + extensions/git/src/commands.ts | 54 ++++++++++++++++++++++++++------- 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/extensions/git/package.json b/extensions/git/package.json index 600196ef2ce2..7b436cb3ca10 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -490,6 +490,12 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.deleteRemoteBranch", + "title": "%command.deleteRemoteBranch%", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.renameBranch", "title": "%command.renameBranch%", @@ -1224,6 +1230,10 @@ "command": "git.deleteBranch", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, + { + "command": "git.deleteRemoteBranch", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + }, { "command": "git.renameBranch", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" @@ -2437,6 +2447,10 @@ "command": "git.deleteBranch", "group": "3_modify@2" }, + { + "command": "git.deleteRemoteBranch", + "group": "3_modify@3" + }, { "command": "git.publish", "group": "4_publish@1" diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 5cfb2b1a7b24..06b2a6ac32d8 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -65,6 +65,7 @@ "command.branch": "Create Branch...", "command.branchFrom": "Create Branch From...", "command.deleteBranch": "Delete Branch...", + "command.deleteRemoteBranch": "Delete Remote Branch...", "command.renameBranch": "Rename Branch...", "command.cherryPick": "Cherry Pick...", "command.cherryPickAbort": "Abort Cherry Pick", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 497edcb9e8e4..0957890e09f9 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -155,8 +155,11 @@ class CheckoutTagItem extends RefItem { class BranchDeleteItem extends RefItem { async run(repository: Repository, force?: boolean): Promise { - if (this.ref.name) { - await repository.deleteBranch(this.ref.name, force); + if (this.ref.type === RefType.Head && this.refName) { + await repository.deleteBranch(this.refName, force); + } else if (this.ref.type === RefType.RemoteHead && this.refRemote && this.refName) { + const refName = this.refName.substring(this.refRemote.length + 1); + await repository.deleteRemoteRef(this.refRemote, refName, { force }); } } } @@ -2883,7 +2886,7 @@ export class CommandCenter { @command('git.deleteBranch', { repository: true }) async deleteBranch(repository: Repository, name: string | undefined, force?: boolean): Promise { - await this._deleteBranch(repository, name, force); + await this._deleteBranch(repository, undefined, name, { remote: false, force }); } @command('git.graph.deleteBranch', { repository: true }) @@ -2893,22 +2896,51 @@ export class CommandCenter { return; } - await this._deleteBranch(repository, historyItemRef.name); + await this._deleteBranch(repository, undefined, historyItemRef.name, { remote: false }); + } + + @command('git.deleteRemoteBranch', { repository: true }) + async deleteRemoteBranch(repository: Repository): Promise { + await this._deleteBranch(repository, undefined, undefined, { remote: true }); } - private async _deleteBranch(repository: Repository, name: string | undefined, force?: boolean): Promise { + private async _deleteBranch(repository: Repository, remote: string | undefined, name: string | undefined, options: { remote: boolean; force?: boolean }): Promise { let run: (force?: boolean) => Promise; - if (typeof name === 'string') { + + if (!options.remote && typeof name === 'string') { + // Local branch run = force => repository.deleteBranch(name!, force); + } else if (options.remote && typeof remote === 'string' && typeof name === 'string') { + // Remote branch + run = force => repository.deleteRemoteRef(remote, name!, { force }); } else { const getBranchPicks = async () => { - const refs = await repository.getRefs({ pattern: 'refs/heads' }); - const currentHead = repository.HEAD && repository.HEAD.name; + const pattern = options.remote ? 'refs/remotes' : 'refs/heads'; + const refs = await repository.getRefs({ pattern }); + + const refsToExclude: string[] = []; + if (options.remote) { + refsToExclude.push('origin/HEAD'); - return refs.filter(ref => ref.name !== currentHead).map(ref => new BranchDeleteItem(ref)); + if (repository.HEAD?.upstream) { + // Current branch's upstream + refsToExclude.push(`${repository.HEAD.upstream.remote}/${repository.HEAD.upstream.name}`); + } + } else { + if (repository.HEAD?.name) { + // Current branch + refsToExclude.push(repository.HEAD.name); + } + } + + return refs.filter(ref => ref.name && !refsToExclude.includes(ref.name)) + .map(ref => new BranchDeleteItem(ref)); }; - const placeHolder = l10n.t('Select a branch to delete'); + const placeHolder = !options.remote + ? l10n.t('Select a branch to delete') + : l10n.t('Select a remote branch to delete'); + const choice = await this.pickRef(getBranchPicks(), placeHolder); if (!choice || !choice.refName) { @@ -2919,7 +2951,7 @@ export class CommandCenter { } try { - await run(force); + await run(options.force); } catch (err) { if (err.gitErrorCode !== GitErrorCodes.BranchNotFullyMerged) { throw err; From c887687cadbe3570308cd346c8c63ec8fe3db6b5 Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Tue, 4 Feb 2025 18:46:14 -0800 Subject: [PATCH 0394/2632] remove redundant service creation in the tests --- .../test/common/promptSyntax/service/promptSyntaxService.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptSyntaxService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptSyntaxService.test.ts index 65c876889897..48716edbd6f9 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptSyntaxService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptSyntaxService.test.ts @@ -111,7 +111,6 @@ suite('PromptSyntaxService', () => { instantiationService = disposables.add(new TestInstantiationService()); instantiationService.stub(ILogService, new NullLogService()); instantiationService.stub(IConfigurationService, new TestConfigurationService()); - instantiationService.stub(IFileService, new TestConfigurationService()); instantiationService.stub(IFileService, disposables.add(instantiationService.createInstance(FileService))); service = disposables.add(instantiationService.createInstance(PromptSyntaxService)); From 13615eed0afc89270699e3a2aaedefffc1434f7d Mon Sep 17 00:00:00 2001 From: Oleg Solomko Date: Tue, 4 Feb 2025 22:03:12 -0800 Subject: [PATCH 0395/2632] reuse common `createURI` test utility --- .../service/promptSyntaxService.test.ts | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptSyntaxService.test.ts b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptSyntaxService.test.ts index 48716edbd6f9..a62673525525 100644 --- a/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptSyntaxService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/promptSyntax/service/promptSyntaxService.test.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; +import { createURI } from '../testUtils/createUri.js'; import { URI } from '../../../../../../../base/common/uri.js'; -import { isWindows } from '../../../../../../../base/common/platform.js'; import { Range } from '../../../../../../../editor/common/core/range.js'; import { assertDefined } from '../../../../../../../base/common/types.js'; import { waitRandom } from '../../../../../../../base/test/common/testUtils.js'; @@ -89,18 +89,6 @@ const assertLinks = ( ); }; -/** - * Creates cross-platform URI. On Windows, absolute paths - * are prefixed with the disk name. - */ -const createURI = (linkPath: string): URI => { - if (isWindows && linkPath.startsWith('/')) { - return URI.file('/d:' + linkPath); - } - - return URI.file(linkPath); -}; - suite('PromptSyntaxService', () => { const disposables = ensureNoDisposablesAreLeakedInTestSuite(); From 3834de8f311b7a3227952910088c6b34abd2a2ba Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Wed, 5 Feb 2025 17:04:37 +0100 Subject: [PATCH 0396/2632] chore - move globalEditingSession cleanup (#239698) * `ChatDecorationsProvider` doesn't works with all editing sessions * `ChatEditingMultiDiffSourceResolver` doesn't use singleton session anymore https://github.com/microsoft/vscode-copilot/issues/12820 * `ChatEditingSnapshotTextModelContentProvider` and `ChatEditingTextModelContentProvider` don't use global editing session anymore * dispose all session on shutdown * move multifile accept/discard into separate actions --- .../browser/chatEditing/chatEditingActions.ts | 13 +---- .../chatEditing/chatEditingEditorActions.ts | 56 ++++++++++++++++++- .../chatEditingModifiedFileEntry.ts | 4 +- .../chatEditing/chatEditingServiceImpl.ts | 43 ++++++++------ .../browser/chatEditing/chatEditingSession.ts | 2 +- .../chatEditingTextModelContentProviders.ts | 41 ++++++-------- .../contrib/chat/common/chatEditingService.ts | 4 +- 7 files changed, 103 insertions(+), 60 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts index 74c5d98582a6..1e759cb59871 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts @@ -292,12 +292,7 @@ export class ChatEditingAcceptAllAction extends EditingSessionAction { weight: KeybindingWeight.WorkbenchContrib, }, menu: [ - { - when: ContextKeyExpr.equals('resourceScheme', CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME), - id: MenuId.EditorTitle, - order: 0, - group: 'navigation', - }, + { id: MenuId.ChatEditingWidgetToolbar, group: 'navigation', @@ -324,12 +319,6 @@ export class ChatEditingDiscardAllAction extends EditingSessionAction { tooltip: localize('discardAllEdits', 'Discard All Edits'), precondition: ContextKeyExpr.and(ChatContextKeys.requestInProgress.negate(), hasUndecidedChatEditingResourceContextKey), menu: [ - { - when: ContextKeyExpr.equals('resourceScheme', CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME), - id: MenuId.EditorTitle, - order: 1, - group: 'navigation', - }, { id: MenuId.ChatEditingWidgetToolbar, group: 'navigation', diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts index 072855972ec1..ac33f23c462c 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts @@ -14,12 +14,16 @@ import { ChatEditorController, ctxHasEditorModification, ctxReviewModeEnabled } import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js'; import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js'; import { ACTIVE_GROUP, IEditorService } from '../../../../services/editor/common/editorService.js'; -import { IChatEditingService } from '../../common/chatEditingService.js'; +import { CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, IChatEditingService } from '../../common/chatEditingService.js'; import { ChatContextKeys } from '../../common/chatContextKeys.js'; import { isEqual } from '../../../../../base/common/resources.js'; import { Range } from '../../../../../editor/common/core/range.js'; import { getNotebookEditorFromEditorPane } from '../../../notebook/browser/notebookBrowser.js'; import { ctxNotebookHasEditorModification } from '../../../notebook/browser/contrib/chatEdit/notebookChatEditContext.js'; +import { resolveCommandsContext } from '../../../../browser/parts/editor/editorCommandsContext.js'; +import { IListService } from '../../../../../platform/list/browser/listService.js'; +import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js'; +import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js'; abstract class NavigateAction extends Action2 { @@ -320,6 +324,53 @@ export class ReviewChangesAction extends EditorAction2 { } } + +// --- multi file diff + +abstract class MultiDiffAcceptDiscardAction extends Action2 { + + constructor(readonly accept: boolean) { + super({ + id: accept ? 'chatEditing.multidiff.acceptAllFiles' : 'chatEditing.multidiff.discardAllFiles', + title: accept ? localize('accept3', 'Accept All Edits') : localize('discard3', 'Discard All Edits'), + icon: accept ? Codicon.check : Codicon.discard, + menu: { + when: ContextKeyExpr.equals('resourceScheme', CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME), + id: MenuId.EditorTitle, + order: accept ? 0 : 1, + group: 'navigation', + }, + }); + } + + async run(accessor: ServicesAccessor, ...args: unknown[]): Promise { + const chatEditingService = accessor.get(IChatEditingService); + const editorService = accessor.get(IEditorService); + const editorGroupsService = accessor.get(IEditorGroupsService); + const listService = accessor.get(IListService); + + const resolvedContext = resolveCommandsContext(args, editorService, editorGroupsService, listService); + + const groupContext = resolvedContext.groupedEditors[0]; + if (!groupContext) { + return; + } + + const editor = groupContext.editors[0]; + if (!(editor instanceof MultiDiffEditorInput) || !editor.resource) { + return; + } + + const session = chatEditingService.getEditingSession(editor.resource.authority); + if (this.accept) { + await session?.accept(); + } else { + await session?.reject(); + } + } +} + + export function registerChatEditorActions() { registerAction2(class NextAction extends NavigateAction { constructor() { super(true); } }); registerAction2(class PrevAction extends NavigateAction { constructor() { super(false); } }); @@ -330,6 +381,9 @@ export function registerChatEditorActions() { registerAction2(RejectHunkAction); registerAction2(OpenDiffAction); + registerAction2(class extends MultiDiffAcceptDiscardAction { constructor() { super(true); } }); + registerAction2(class extends MultiDiffAcceptDiscardAction { constructor() { super(false); } }); + MenuRegistry.appendMenuItem(MenuId.ChatEditingEditorContent, { command: { id: navigationBearingFakeActionId, diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts index 46068b2e8179..b9a77cddff6b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts @@ -180,7 +180,7 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie modelService.createModel( createTextBufferFactoryFromSnapshot(initialContent ? stringToSnapshot(initialContent) : this.doc.createSnapshot()), languageService.createById(this.doc.getLanguageId()), - ChatEditingTextModelContentProvider.getFileURI(this.entryId, this.modifiedURI.path), + ChatEditingTextModelContentProvider.getFileURI(_telemetryInfo.sessionId, this.entryId, this.modifiedURI.path), false ) ); @@ -270,7 +270,7 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie return { resource: this.modifiedURI, languageId: this.modifiedModel.getLanguageId(), - snapshotUri: ChatEditingSnapshotTextModelContentProvider.getSnapshotFileURI(requestId, this.modifiedURI.path), + snapshotUri: ChatEditingSnapshotTextModelContentProvider.getSnapshotFileURI(this._telemetryInfo.sessionId, requestId, this.modifiedURI.path), original: this.originalModel.getValue(), current: this.modifiedModel.getValue(), originalToCurrentEdit: this._edit, diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts index e49f8637673a..0098bdadb66a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts @@ -10,7 +10,7 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { BugIndicatingError, ErrorNoTelemetry } from '../../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../../base/common/event.js'; import { Iterable } from '../../../../../base/common/iterator.js'; -import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; +import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from '../../../../../base/common/lifecycle.js'; import { LinkedList } from '../../../../../base/common/linkedList.js'; import { ResourceMap } from '../../../../../base/common/map.js'; import { Schemas } from '../../../../../base/common/network.js'; @@ -104,15 +104,15 @@ export class ChatEditingService extends Disposable implements IChatEditingServic @IProductService productService: IProductService, ) { super(); - this._register(decorationsService.registerDecorationsProvider(_instantiationService.createInstance(ChatDecorationsProvider, this._currentSessionObs))); - this._register(multiDiffSourceResolverService.registerResolver(_instantiationService.createInstance(ChatEditingMultiDiffSourceResolver, this._currentSessionObs))); - this._register(textModelService.registerTextModelContentProvider(ChatEditingTextModelContentProvider.scheme, _instantiationService.createInstance(ChatEditingTextModelContentProvider, this._currentSessionObs))); - this._register(textModelService.registerTextModelContentProvider(ChatEditingSnapshotTextModelContentProvider.scheme, _instantiationService.createInstance(ChatEditingSnapshotTextModelContentProvider, this._currentSessionObs))); + this._register(decorationsService.registerDecorationsProvider(_instantiationService.createInstance(ChatDecorationsProvider, this.editingSessionsObs))); + this._register(multiDiffSourceResolverService.registerResolver(_instantiationService.createInstance(ChatEditingMultiDiffSourceResolver))); + this._register(textModelService.registerTextModelContentProvider(ChatEditingTextModelContentProvider.scheme, _instantiationService.createInstance(ChatEditingTextModelContentProvider))); + this._register(textModelService.registerTextModelContentProvider(ChatEditingSnapshotTextModelContentProvider.scheme, _instantiationService.createInstance(ChatEditingSnapshotTextModelContentProvider))); this._register(this._chatService.onDidDisposeSession((e) => { - if (e.reason === 'cleared' && this._currentSessionObs.get()?.chatSessionId === e.sessionId) { - void this._currentSessionObs.get()?.stop(); + if (e.reason === 'cleared') { + this.getEditingSession(e.sessionId)?.stop(); } })); @@ -156,6 +156,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic override dispose(): void { this._currentSessionObs.get()?.dispose(); + dispose(this._adhocSessionsObs.get()); super.dispose(); } @@ -434,15 +435,18 @@ class ChatDecorationsProvider extends Disposable implements IDecorationsProvider readonly label: string = localize('chat', "Chat Editing"); private readonly _currentEntries = derived(this, (r) => { - const session = this._session.read(r); - if (!session) { + const sessions = this._sessions.read(r); + if (!sessions) { return []; } - const state = session.state.read(r); - if (state === ChatEditingSessionState.Disposed) { - return []; + const result: IModifiedFileEntry[] = []; + for (const session of sessions) { + if (session.state.read(r) !== ChatEditingSessionState.Disposed) { + const entries = session.entries.read(r); + result.push(...entries); + } } - return session.entries.read(r); + return result; }); private readonly _currentlyEditingUris = derived(this, (r) => { @@ -461,7 +465,7 @@ class ChatDecorationsProvider extends Disposable implements IDecorationsProvider ); constructor( - private readonly _session: IObservable, + private readonly _sessions: IObservable, @IChatAgentService private readonly _chatAgentService: IChatAgentService ) { super(); @@ -493,8 +497,8 @@ class ChatDecorationsProvider extends Disposable implements IDecorationsProvider export class ChatEditingMultiDiffSourceResolver implements IMultiDiffSourceResolver { constructor( - private readonly _currentSession: IObservable, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IChatEditingService private readonly _chatEditingService: IChatEditingService ) { } canHandleUri(uri: URI): boolean { @@ -502,7 +506,12 @@ export class ChatEditingMultiDiffSourceResolver implements IMultiDiffSourceResol } async resolveDiffSource(uri: URI): Promise { - return this._instantiationService.createInstance(ChatEditingMultiDiffSource, this._currentSession); + + const thisSession = derived(this, r => { + return this._chatEditingService.editingSessionsObs.read(r).find(candidate => candidate.chatSessionId === uri.authority); + }); + + return this._instantiationService.createInstance(ChatEditingMultiDiffSource, thisSession); } } @@ -532,6 +541,6 @@ class ChatEditingMultiDiffSource implements IResolvedMultiDiffSource { }; constructor( - private readonly _currentSession: IObservable + private readonly _currentSession: IObservable ) { } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts index 36c7bdcfa033..3dd782fe5f1a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts @@ -529,7 +529,7 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio } } const input = MultiDiffEditorInput.fromResourceMultiDiffEditorInput({ - multiDiffSource: getMultiDiffSourceUri(), + multiDiffSource: getMultiDiffSourceUri(this), label: localize('multiDiffEditorInput.name', "Suggested Edits") }, this._instantiationService); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts index e722b40ff942..4afb878b0012 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingTextModelContentProviders.ts @@ -3,36 +3,29 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IObservable } from '../../../../../base/common/observable.js'; import { URI } from '../../../../../base/common/uri.js'; import { ITextModel } from '../../../../../editor/common/model.js'; import { IModelService } from '../../../../../editor/common/services/model.js'; import { ITextModelContentProvider } from '../../../../../editor/common/services/resolverService.js'; +import { IChatEditingService } from '../../common/chatEditingService.js'; import { ChatEditingSession } from './chatEditingSession.js'; -type ChatEditingTextModelContentQueryData = { kind: 'empty' } | { kind: 'doc'; documentId: string }; +type ChatEditingTextModelContentQueryData = { kind: 'doc'; documentId: string; chatSessionId: string }; export class ChatEditingTextModelContentProvider implements ITextModelContentProvider { public static readonly scheme = 'chat-editing-text-model'; - public static getEmptyFileURI(): URI { - return URI.from({ - scheme: ChatEditingTextModelContentProvider.scheme, - query: JSON.stringify({ kind: 'empty' }), - }); - } - - public static getFileURI(documentId: string, path: string): URI { + public static getFileURI(chatSessionId: string, documentId: string, path: string): URI { return URI.from({ scheme: ChatEditingTextModelContentProvider.scheme, path, - query: JSON.stringify({ kind: 'doc', documentId }), + query: JSON.stringify({ kind: 'doc', documentId, chatSessionId } satisfies ChatEditingTextModelContentQueryData), }); } constructor( - private readonly _currentSessionObs: IObservable, - @IModelService private readonly _modelService: IModelService + @IModelService private readonly _modelService: IModelService, + @IChatEditingService private readonly _chatEditingService: IChatEditingService ) { } async provideTextContent(resource: URI): Promise { @@ -42,12 +35,10 @@ export class ChatEditingTextModelContentProvider implements ITextModelContentPro } const data: ChatEditingTextModelContentQueryData = JSON.parse(resource.query); - if (data.kind === 'empty') { - return this._modelService.createModel('', null, resource, false); - } - const session = this._currentSessionObs.get(); - if (!session) { + const session = this._chatEditingService.getEditingSession(data.chatSessionId); + + if (!(session instanceof ChatEditingSession)) { return null; } @@ -55,22 +46,22 @@ export class ChatEditingTextModelContentProvider implements ITextModelContentPro } } -type ChatEditingSnapshotTextModelContentQueryData = { requestId: string | undefined }; +type ChatEditingSnapshotTextModelContentQueryData = { sessionId: string; requestId: string | undefined }; export class ChatEditingSnapshotTextModelContentProvider implements ITextModelContentProvider { public static readonly scheme = 'chat-editing-snapshot-text-model'; - public static getSnapshotFileURI(requestId: string | undefined, path: string): URI { + public static getSnapshotFileURI(chatSessionId: string, requestId: string | undefined, path: string): URI { return URI.from({ scheme: ChatEditingSnapshotTextModelContentProvider.scheme, path, - query: JSON.stringify({ requestId: requestId ?? '' }), + query: JSON.stringify({ sessionId: chatSessionId, requestId: requestId ?? '' } satisfies ChatEditingSnapshotTextModelContentQueryData), }); } constructor( - private readonly _currentSessionObs: IObservable, - @IModelService private readonly _modelService: IModelService + @IModelService private readonly _modelService: IModelService, + @IChatEditingService private readonly _chatEditingService: IChatEditingService ) { } async provideTextContent(resource: URI): Promise { @@ -81,8 +72,8 @@ export class ChatEditingSnapshotTextModelContentProvider implements ITextModelCo const data: ChatEditingSnapshotTextModelContentQueryData = JSON.parse(resource.query); - const session = this._currentSessionObs.get(); - if (!session || !data.requestId) { + const session = this._chatEditingService.getEditingSession(data.sessionId); + if (!(session instanceof ChatEditingSession) || !data.requestId) { return null; } diff --git a/src/vs/workbench/contrib/chat/common/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/chatEditingService.ts index dc2a4a951209..cb704a048088 100644 --- a/src/vs/workbench/contrib/chat/common/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/chatEditingService.ts @@ -191,9 +191,9 @@ export function isChatEditingActionContext(thing: unknown): thing is IChatEditin return typeof thing === 'object' && !!thing && 'sessionId' in thing; } -export function getMultiDiffSourceUri(): URI { +export function getMultiDiffSourceUri(session: IChatEditingSession): URI { return URI.from({ scheme: CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, - path: '', + authority: session.chatSessionId, }); } From af22e57ed637fdfcbe14d09a50e1ef73b1dd9c02 Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Wed, 5 Feb 2025 17:36:20 +0100 Subject: [PATCH 0397/2632] Git - add a check so that the active branch cannot be deleted (#239700) --- extensions/git/src/commands.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 0957890e09f9..c7fe7d5616ed 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -2896,6 +2896,11 @@ export class CommandCenter { return; } + if (historyItemRef.id === repository.historyProvider.currentHistoryItemRef?.id) { + window.showInformationMessage(l10n.t('The active branch cannot be deleted.')); + return; + } + await this._deleteBranch(repository, undefined, historyItemRef.name, { remote: false }); } From 5446d62f193ed124a0ff5ad24ec98ab1aa5d54d1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 5 Feb 2025 08:38:04 -0800 Subject: [PATCH 0398/2632] Don't show suggest widget when last input was arrow Fixes #239609 --- .../suggest/browser/terminalSuggestAddon.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts index cfaa70a0883a..651bbba27c00 100644 --- a/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts +++ b/src/vs/workbench/contrib/terminalContrib/suggest/browser/terminalSuggestAddon.ts @@ -258,6 +258,12 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest await this._handleCompletionProviders(this._terminal, token, explicitlyInvoked); } + private _wasLastInputArrowKey(): boolean { + // Never request completions if the last key sequence was up or down as the user was likely + // navigating history + return !!this._lastUserData?.match(/^\x1b[\[O]?[A-D]$/); + } + private _sync(promptInputState: IPromptInputModelState): void { const config = this._configurationService.getValue(terminalSuggestConfigSection); if (!this._mostRecentPromptInputState || promptInputState.cursorIndex > this._mostRecentPromptInputState.cursorIndex) { @@ -268,9 +274,7 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest if (!this._terminalSuggestWidgetVisibleContextKey.get()) { if (config.quickSuggestions) { if (promptInputState.prefix.match(/[^\s]$/)) { - // Never request completions if the last key sequence was up or down as the user was likely - // navigating history - if (!this._lastUserData?.match(/^\x1b[\[O]?[A-D]$/)) { + if (!this._wasLastInputArrowKey()) { this.requestCompletions(); sent = true; } @@ -289,8 +293,10 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest // with git branches in particular this._isFilteringDirectories && prefix?.match(/[\\\/]$/) ) { - this.requestCompletions(); - sent = true; + if (!this._wasLastInputArrowKey()) { + this.requestCompletions(); + sent = true; + } } if (!sent) { for (const provider of this._terminalCompletionService.providers) { @@ -299,8 +305,10 @@ export class SuggestAddon extends Disposable implements ITerminalAddon, ISuggest } for (const char of provider.triggerCharacters) { if (prefix?.endsWith(char)) { - this.requestCompletions(); - sent = true; + if (!this._wasLastInputArrowKey()) { + this.requestCompletions(); + sent = true; + } break; } } From d467ad19ff15c1a3b5bb2a6a858ca59f13a113a0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 5 Feb 2025 17:55:47 +0100 Subject: [PATCH 0399/2632] debt - move actions around to code editor --- .../parts/editor/editor.contribution.ts | 5 +-- .../browser/parts/editor/editorActions.ts | 30 ------------- .../browser/parts/editor/editorGroupView.ts | 2 +- .../browser/codeEditor.contribution.ts | 1 + .../codeEditor/browser/toggleOvertype.ts | 43 +++++++++++++++++++ 5 files changed, 46 insertions(+), 35 deletions(-) create mode 100644 src/vs/workbench/contrib/codeEditor/browser/toggleOvertype.ts diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 8c75858d9810..1c777faacaf8 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -43,8 +43,7 @@ import { SplitEditorToFirstGroupAction, SplitEditorToLastGroupAction, SplitEditorToLeftGroupAction, SplitEditorToNextGroupAction, SplitEditorToPreviousGroupAction, SplitEditorToRightGroupAction, NavigateForwardInEditsAction, NavigateBackwardsInEditsAction, NavigateForwardInNavigationsAction, NavigateBackwardsInNavigationsAction, NavigatePreviousInNavigationsAction, NavigatePreviousInEditsAction, NavigateToLastNavigationLocationAction, MaximizeGroupHideSidebarAction, MoveEditorToNewWindowAction, CopyEditorToNewindowAction, RestoreEditorsToMainWindowAction, ToggleMaximizeEditorGroupAction, MinimizeOtherGroupsHideSidebarAction, CopyEditorGroupToNewWindowAction, - MoveEditorGroupToNewWindowAction, NewEmptyEditorWindowAction, - ToggleOvertypeInsertMode + MoveEditorGroupToNewWindowAction, NewEmptyEditorWindowAction } from './editorActions.js'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_EDITOR_GROUP_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, @@ -172,8 +171,6 @@ quickAccessRegistry.registerQuickAccessProvider({ //#region Actions & Commands -registerAction2(ToggleOvertypeInsertMode); - registerAction2(ChangeLanguageAction); registerAction2(ChangeEOLAction); registerAction2(ChangeEncodingAction); diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index fc9b8ec03e0e..c33f71c958e1 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -38,7 +38,6 @@ import { ICommandActionTitle } from '../../../../platform/action/common/action.j import { IProgressService, ProgressLocation } from '../../../../platform/progress/common/progress.js'; import { resolveCommandsContext } from './editorCommandsContext.js'; import { IListService } from '../../../../platform/list/browser/listService.js'; -import { InputMode } from '../../../../editor/common/inputMode.js'; class ExecuteCommandAction extends Action2 { @@ -2697,32 +2696,3 @@ export class NewEmptyEditorWindowAction extends Action2 { auxiliaryEditorPart.activeGroup.focus(); } } - -export class ToggleOvertypeInsertMode extends Action2 { - - constructor() { - super({ - id: 'editor.action.toggleOvertypeInsertMode', - title: { - ...localize2('toggleOvertypeInsertMode', "Toggle Overtype/Insert Mode"), - mnemonicTitle: localize({ key: 'mitoggleOvertypeInsertMode', comment: ['&& denotes a mnemonic'] }, "&&Toggle Overtype/Insert Mode"), - }, - metadata: { - description: localize2('toggleOvertypeMode.description', "Toggle between overtype and insert mode"), - }, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyCode.Insert, - mac: { primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.KeyO }, - }, - f1: true, - category: Categories.View - }); - } - - override async run(accessor: ServicesAccessor): Promise { - const oldInputMode = InputMode.getInputMode(); - const newInputMode = oldInputMode === 'insert' ? 'overtype' : 'insert'; - InputMode.setInputMode(newInputMode); - } -} diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 1dff715f7036..4d50123c07da 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1467,7 +1467,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const options = fillActiveEditorViewState(this, editor, { ...openOptions, pinned: true, // always pin moved editor - sticky: openOptions?.sticky ?? (!keepCopy && this.model.isSticky(editor)) // preserve sticky state only if editor is moved or eplicitly wanted (https://github.com/microsoft/vscode/issues/99035) + sticky: openOptions?.sticky ?? (!keepCopy && this.model.isSticky(editor)) // preserve sticky state only if editor is moved or explicitly wanted (https://github.com/microsoft/vscode/issues/99035) }); // Indicate will move event diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index ac45ecadd875..4e42d6cd7c45 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -16,6 +16,7 @@ import './quickaccess/gotoSymbolQuickAccess.js'; import './saveParticipants.js'; import './toggleColumnSelection.js'; import './toggleMinimap.js'; +import './toggleOvertype.js'; import './toggleMultiCursorModifier.js'; import './toggleRenderControlCharacter.js'; import './toggleRenderWhitespace.js'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleOvertype.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleOvertype.ts new file mode 100644 index 000000000000..b607bf0c8ff2 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleOvertype.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize, localize2 } from '../../../../nls.js'; +import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; +import { Categories } from '../../../../platform/action/common/actionCommonCategories.js'; +import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { KeybindingWeight } from '../../../../platform/keybinding/common/keybindingsRegistry.js'; +import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; +import { InputMode } from '../../../../editor/common/inputMode.js'; + +export class ToggleOvertypeInsertMode extends Action2 { + + constructor() { + super({ + id: 'editor.action.toggleOvertypeInsertMode', + title: { + ...localize2('toggleOvertypeInsertMode', "Toggle Overtype/Insert Mode"), + mnemonicTitle: localize({ key: 'mitoggleOvertypeInsertMode', comment: ['&& denotes a mnemonic'] }, "&&Toggle Overtype/Insert Mode"), + }, + metadata: { + description: localize2('toggleOvertypeMode.description', "Toggle between overtype and insert mode"), + }, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyCode.Insert, + mac: { primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.KeyO }, + }, + f1: true, + category: Categories.View + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const oldInputMode = InputMode.getInputMode(); + const newInputMode = oldInputMode === 'insert' ? 'overtype' : 'insert'; + InputMode.setInputMode(newInputMode); + } +} + +registerAction2(ToggleOvertypeInsertMode); From 39cf1ffb98036dc04eb0a4d70c618c481963d0a7 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Wed, 5 Feb 2025 18:58:39 +0100 Subject: [PATCH 0400/2632] Adopt Tree-Sitter 0.25.1 (#239683) * Adopt Tree-Sitter 0.25.1 * Update @vscode/tree-sitter-wasm * Fix incorrect redirect --- build/gulpfile.editor.js | 2 +- package-lock.json | 8 +-- package.json | 2 +- remote/package-lock.json | 8 +-- remote/package.json | 2 +- remote/web/package-lock.json | 8 +-- remote/web/package.json | 2 +- src/vs/editor/common/languages.ts | 2 +- .../treeSitter/treeSitterParserService.ts | 66 ++++++++----------- .../services/treeSitterParserService.ts | 50 +++++++++++++- .../browser/standaloneTreeSitterService.ts | 2 +- .../services/treeSitterParserService.test.ts | 58 +++++++++++----- .../common/services/testTreeSitterService.ts | 2 +- .../inspectEditorTokens.ts | 8 +-- .../browser/themes.test.contribution.ts | 2 +- ...eSitterTokenizationFeature.contribution.ts | 3 +- .../browser/treeSitterTokenizationFeature.ts | 13 ++-- .../treeSitterTokenizationFeature.test.ts | 3 +- 18 files changed, 156 insertions(+), 85 deletions(-) diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 739c37fc677b..9f7ea57465ba 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -80,7 +80,7 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { importIgnorePattern: /\.css$/, destRoot: path.join(root, 'out-editor-src'), redirects: { - '@vscode/tree-sitter-wasm': '../node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-web', + '@vscode/tree-sitter-wasm': '../node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter', } }); }); diff --git a/package-lock.json b/package-lock.json index 220b5d9879b3..d20dc2d09503 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@vscode/spdlog": "^0.15.0", "@vscode/sqlite3": "5.1.8-vscode", "@vscode/sudo-prompt": "9.3.1", - "@vscode/tree-sitter-wasm": "^0.0.5", + "@vscode/tree-sitter-wasm": "^0.1.1", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", @@ -3157,9 +3157,9 @@ } }, "node_modules/@vscode/tree-sitter-wasm": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.0.5.tgz", - "integrity": "sha512-qA+BkB2UgkfXMQVGsqPeG3vR3pXv0inP6WQ/dq6BALy7dIX9KQvGXvDCiqehdFvZZO4tDFt4qb5DdSsvwR4Y9Q==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.1.1.tgz", + "integrity": "sha512-2KHGbX2krHP/LyfpDB6QnSAqyqIL7N2Md7KTMuoqq8+GhUzrgXwIClhpm7lqL/isNTYVzsEubR7YI6f2YfAqqQ==", "license": "MIT" }, "node_modules/@vscode/v8-heap-parser": { diff --git a/package.json b/package.json index 3f97b4a3d115..2cd4edb0bbcf 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "@vscode/spdlog": "^0.15.0", "@vscode/sqlite3": "5.1.8-vscode", "@vscode/sudo-prompt": "9.3.1", - "@vscode/tree-sitter-wasm": "^0.0.5", + "@vscode/tree-sitter-wasm": "^0.1.1", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-mutex": "^0.5.0", "@vscode/windows-process-tree": "^0.6.0", diff --git a/remote/package-lock.json b/remote/package-lock.json index 787c3c9ba96a..a53da8875922 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -16,7 +16,7 @@ "@vscode/proxy-agent": "^0.31.0", "@vscode/ripgrep": "^1.15.10", "@vscode/spdlog": "^0.15.0", - "@vscode/tree-sitter-wasm": "^0.0.5", + "@vscode/tree-sitter-wasm": "^0.1.1", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", @@ -470,9 +470,9 @@ } }, "node_modules/@vscode/tree-sitter-wasm": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.0.5.tgz", - "integrity": "sha512-qA+BkB2UgkfXMQVGsqPeG3vR3pXv0inP6WQ/dq6BALy7dIX9KQvGXvDCiqehdFvZZO4tDFt4qb5DdSsvwR4Y9Q==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.1.1.tgz", + "integrity": "sha512-2KHGbX2krHP/LyfpDB6QnSAqyqIL7N2Md7KTMuoqq8+GhUzrgXwIClhpm7lqL/isNTYVzsEubR7YI6f2YfAqqQ==", "license": "MIT" }, "node_modules/@vscode/vscode-languagedetection": { diff --git a/remote/package.json b/remote/package.json index 5cc1ff194680..8dc72f471db9 100644 --- a/remote/package.json +++ b/remote/package.json @@ -11,7 +11,7 @@ "@vscode/proxy-agent": "^0.31.0", "@vscode/ripgrep": "^1.15.10", "@vscode/spdlog": "^0.15.0", - "@vscode/tree-sitter-wasm": "^0.0.5", + "@vscode/tree-sitter-wasm": "^0.1.1", "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.6.0", "@vscode/windows-registry": "^1.1.0", diff --git a/remote/web/package-lock.json b/remote/web/package-lock.json index 45f7724805de..547912df017a 100644 --- a/remote/web/package-lock.json +++ b/remote/web/package-lock.json @@ -11,7 +11,7 @@ "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/tree-sitter-wasm": "^0.0.5", + "@vscode/tree-sitter-wasm": "^0.1.1", "@vscode/vscode-languagedetection": "1.0.21", "@xterm/addon-clipboard": "^0.2.0-beta.79", "@xterm/addon-image": "^0.9.0-beta.96", @@ -76,9 +76,9 @@ "integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==" }, "node_modules/@vscode/tree-sitter-wasm": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.0.5.tgz", - "integrity": "sha512-qA+BkB2UgkfXMQVGsqPeG3vR3pXv0inP6WQ/dq6BALy7dIX9KQvGXvDCiqehdFvZZO4tDFt4qb5DdSsvwR4Y9Q==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@vscode/tree-sitter-wasm/-/tree-sitter-wasm-0.1.1.tgz", + "integrity": "sha512-2KHGbX2krHP/LyfpDB6QnSAqyqIL7N2Md7KTMuoqq8+GhUzrgXwIClhpm7lqL/isNTYVzsEubR7YI6f2YfAqqQ==", "license": "MIT" }, "node_modules/@vscode/vscode-languagedetection": { diff --git a/remote/web/package.json b/remote/web/package.json index f1f5ce1ecb94..47350e2deaf2 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -6,7 +6,7 @@ "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/tree-sitter-wasm": "^0.0.5", + "@vscode/tree-sitter-wasm": "^0.1.1", "@vscode/vscode-languagedetection": "1.0.21", "@xterm/addon-clipboard": "^0.2.0-beta.79", "@xterm/addon-image": "^0.9.0-beta.96", diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 76daa762217e..a8f1af273f44 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -27,7 +27,7 @@ import { localize } from '../../nls.js'; import { ExtensionIdentifier } from '../../platform/extensions/common/extensions.js'; import { IMarkerData } from '../../platform/markers/common/markers.js'; import { IModelTokensChangedEvent } from './textModelEvents.js'; -import type { Parser } from '@vscode/tree-sitter-wasm'; +import type * as Parser from '@vscode/tree-sitter-wasm'; import { ITextModel } from './model.js'; import { TokenUpdate } from './model/tokenStore.js'; diff --git a/src/vs/editor/common/services/treeSitter/treeSitterParserService.ts b/src/vs/editor/common/services/treeSitter/treeSitterParserService.ts index 4ca0e3396de8..fb8c0c912c48 100644 --- a/src/vs/editor/common/services/treeSitter/treeSitterParserService.ts +++ b/src/vs/editor/common/services/treeSitter/treeSitterParserService.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { Parser } from '@vscode/tree-sitter-wasm'; +import type * as Parser from '@vscode/tree-sitter-wasm'; import { AppResourcePath, FileAccess, nodeModulesAsarUnpackedPath, nodeModulesPath } from '../../../../base/common/network.js'; -import { EDITOR_EXPERIMENTAL_PREFER_TREESITTER, ITreeSitterParserService, ITreeSitterParseResult, ITextModelTreeSitter, RangeChange, TreeUpdateEvent, TreeParseUpdateEvent } from '../treeSitterParserService.js'; +import { EDITOR_EXPERIMENTAL_PREFER_TREESITTER, ITreeSitterParserService, ITreeSitterParseResult, ITextModelTreeSitter, RangeChange, TreeUpdateEvent, TreeParseUpdateEvent, ITreeSitterImporter } from '../treeSitterParserService.js'; import { IModelService } from '../model.js'; import { Disposable, DisposableMap, DisposableStore, dispose, IDisposable } from '../../../../base/common/lifecycle.js'; import { ITextModel } from '../../model.js'; @@ -15,7 +15,7 @@ import { ITelemetryService } from '../../../../platform/telemetry/common/telemet import { ILogService } from '../../../../platform/log/common/log.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { setTimeout0 } from '../../../../base/common/platform.js'; -import { canASAR, importAMDNodeModule } from '../../../../amdX.js'; +import { canASAR } from '../../../../amdX.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { CancellationToken, cancelOnDispose } from '../../../../base/common/cancellation.js'; import { IEnvironmentService } from '../../../../platform/environment/common/environment.js'; @@ -44,7 +44,7 @@ export class TextModelTreeSitter extends Disposable implements ITextModelTreeSit constructor(readonly model: ITextModel, private readonly _treeSitterLanguages: TreeSitterLanguages, - private readonly _treeSitterImporter: TreeSitterImporter, + private readonly _treeSitterImporter: ITreeSitterImporter, private readonly _logService: ILogService, private readonly _telemetryService: ITelemetryService, parseImmediately: boolean = true @@ -155,11 +155,10 @@ export class TreeSitterParseResult implements IDisposable, ITreeSitterParseResul return this._versionId; } private _isDisposed: boolean = false; - constructor(public readonly parser: Parser, + constructor(public readonly parser: Parser.Parser, public /** exposed for tests **/ readonly language: Parser.Language, private readonly _logService: ILogService, private readonly _telemetryService: ITelemetryService) { - this.parser.setTimeoutMicros(50 * 1000); // 50 ms this.parser.setLanguage(language); } dispose(): void { @@ -228,7 +227,7 @@ export class TreeSitterParseResult implements IDisposable, ITreeSitterParseResul return false; }; - const getClosestPreviousNodes = (): { old: Parser.SyntaxNode; new: Parser.SyntaxNode } | undefined => { + const getClosestPreviousNodes = (): { old: Parser.Node; new: Parser.Node } | undefined => { // Go up parents until the end of the parent is before the start of the current. const newFindPrev = newTree.walk(); newFindPrev.resetTo(newCursor); @@ -267,10 +266,10 @@ export class TreeSitterParseResult implements IDisposable, ITreeSitterParseResul const newChildren = newCursor.currentNode.children; const indexChangedChildren: number[] = []; const changedChildren = newChildren.filter((c, index) => { - if (c.hasChanges) { + if (c?.hasChanges) { indexChangedChildren.push(index); } - return c.hasChanges; + return c?.hasChanges; }); if (changedChildren.length >= 1) { next = gotoNthChild(indexChangedChildren[0]); @@ -412,12 +411,13 @@ export class TreeSitterParseResult implements IDisposable, ITreeSitterParseResul let time: number = 0; let passes: number = 0; const inProgressVersion = this._editVersion; - let newTree: Parser.Tree | undefined; + let newTree: Parser.Tree | null | undefined; + this._lastYieldTime = performance.now(); do { const timer = performance.now(); try { - newTree = this.parser.parse((index: number, position?: Parser.Point) => this._parseCallback(model, index), this._tree); + newTree = this.parser.parse((index: number, position?: Parser.Point) => this._parseCallback(model, index), this._tree, { progressCallback: this._parseProgressCallback.bind(this) }); } catch (e) { // parsing can fail when the timeout is reached, will resume upon next loop } finally { @@ -433,13 +433,23 @@ export class TreeSitterParseResult implements IDisposable, ITreeSitterParseResul return (newTree && (inProgressVersion === model.getVersionId())) ? newTree : undefined; } - private _parseCallback(textModel: ITextModel, index: number): string | null { + private _lastYieldTime: number = 0; + private _parseProgressCallback(state: Parser.ParseState) { + const now = performance.now(); + if (now - this._lastYieldTime > 50) { + this._lastYieldTime = now; + return true; + } + return false; + } + + private _parseCallback(textModel: ITextModel, index: number): string | undefined { try { return textModel.getTextBuffer().getNearestChunk(index); } catch (e) { this._logService.debug('Error getting chunk for tree-sitter parsing', e); } - return null; + return undefined; } private sendParseTimeTelemetry(parseType: TelemetryParseType, languageId: string, time: number, passes: number): void { @@ -467,7 +477,7 @@ export class TreeSitterLanguages extends Disposable { */ public readonly onDidAddLanguage: Event<{ id: string; language: Parser.Language }> = this._onDidAddLanguage.event; - constructor(private readonly _treeSitterImporter: TreeSitterImporter, + constructor(private readonly _treeSitterImporter: ITreeSitterImporter, private readonly _fileService: IFileService, private readonly _environmentService: IEnvironmentService, private readonly _registeredLanguages: Map, @@ -514,8 +524,8 @@ export class TreeSitterLanguages extends Disposable { } const wasmPath: AppResourcePath = `${languageLocation}/${grammarName}.wasm`; const languageFile = await (this._fileService.readFile(FileAccess.asFileUri(wasmPath))); - const Parser = await this._treeSitterImporter.getParserClass(); - return Parser.Language.load(languageFile.value.buffer); + const Language = await this._treeSitterImporter.getLanguageClass(); + return Language.load(languageFile.value.buffer); } private _getLanguageLocation(languageId: string): AppResourcePath | undefined { @@ -527,24 +537,6 @@ export class TreeSitterLanguages extends Disposable { } } -export class TreeSitterImporter { - private _treeSitterImport: typeof import('@vscode/tree-sitter-wasm') | undefined; - private async _getTreeSitterImport() { - if (!this._treeSitterImport) { - this._treeSitterImport = await importAMDNodeModule('@vscode/tree-sitter-wasm', 'wasm/tree-sitter.js'); - } - return this._treeSitterImport; - } - - private _parserClass: typeof Parser | undefined; - public async getParserClass() { - if (!this._parserClass) { - this._parserClass = (await this._getTreeSitterImport()).Parser; - } - return this._parserClass; - } -} - interface TextModelTreeSitterItem { dispose(): void; textModelTreeSitter: TextModelTreeSitter; @@ -556,7 +548,6 @@ export class TreeSitterTextModelService extends Disposable implements ITreeSitte private _init!: Promise; private _textModelTreeSitters: DisposableMap = this._register(new DisposableMap()); private readonly _registeredLanguages: Map = new Map(); - private readonly _treeSitterImporter: TreeSitterImporter = new TreeSitterImporter(); private readonly _treeSitterLanguages: TreeSitterLanguages; public readonly onDidAddLanguage: Event<{ id: string; language: Parser.Language }>; @@ -570,7 +561,8 @@ export class TreeSitterTextModelService extends Disposable implements ITreeSitte @ITelemetryService private readonly _telemetryService: ITelemetryService, @ILogService private readonly _logService: ILogService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IEnvironmentService private readonly _environmentService: IEnvironmentService + @IEnvironmentService private readonly _environmentService: IEnvironmentService, + @ITreeSitterImporter private readonly _treeSitterImporter: ITreeSitterImporter ) { super(); this._treeSitterLanguages = this._register(new TreeSitterLanguages(this._treeSitterImporter, fileService, this._environmentService, this._registeredLanguages)); @@ -601,7 +593,7 @@ export class TreeSitterTextModelService extends Disposable implements ITreeSitte if (language) { const parser = new Parser(); parser.setLanguage(language); - return parser.parse(content); + return parser.parse(content) ?? undefined; } return undefined; } diff --git a/src/vs/editor/common/services/treeSitterParserService.ts b/src/vs/editor/common/services/treeSitterParserService.ts index 4d0ddb3f971e..43d480443b26 100644 --- a/src/vs/editor/common/services/treeSitterParserService.ts +++ b/src/vs/editor/common/services/treeSitterParserService.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { Parser } from '@vscode/tree-sitter-wasm'; +import type * as Parser from '@vscode/tree-sitter-wasm'; import { Event } from '../../../base/common/event.js'; import { ITextModel } from '../model.js'; import { createDecorator } from '../../../platform/instantiation/common/instantiation.js'; import { Range } from '../core/range.js'; +import { importAMDNodeModule } from '../../../amdX.js'; export const EDITOR_EXPERIMENTAL_PREFER_TREESITTER = 'editor.experimental.preferTreeSitter'; @@ -57,3 +58,50 @@ export interface ITextModelTreeSitter { parse(languageId?: string): Promise; dispose(): void; } + +export const ITreeSitterImporter = createDecorator('treeSitterImporter'); + +export interface ITreeSitterImporter { + readonly _serviceBrand: undefined; + getParserClass(): Promise; + getLanguageClass(): Promise; + getQueryClass(): Promise; +} + +export class TreeSitterImporter implements ITreeSitterImporter { + readonly _serviceBrand: undefined; + private _treeSitterImport: typeof import('@vscode/tree-sitter-wasm') | undefined; + + constructor() { } + + private async _getTreeSitterImport() { + if (!this._treeSitterImport) { + this._treeSitterImport = await importAMDNodeModule('@vscode/tree-sitter-wasm', 'wasm/tree-sitter.js'); + } + return this._treeSitterImport; + } + + private _parserClass: typeof Parser.Parser | undefined; + public async getParserClass() { + if (!this._parserClass) { + this._parserClass = (await this._getTreeSitterImport()).Parser; + } + return this._parserClass; + } + + private _languageClass: typeof Parser.Language | undefined; + public async getLanguageClass() { + if (!this._languageClass) { + this._languageClass = (await this._getTreeSitterImport()).Language; + } + return this._languageClass; + } + + private _queryClass: typeof Parser.Query | undefined; + public async getQueryClass() { + if (!this._queryClass) { + this._queryClass = (await this._getTreeSitterImport()).Query; + } + return this._queryClass; + } +} diff --git a/src/vs/editor/standalone/browser/standaloneTreeSitterService.ts b/src/vs/editor/standalone/browser/standaloneTreeSitterService.ts index 20a2f082f3c2..c0f0b1354d97 100644 --- a/src/vs/editor/standalone/browser/standaloneTreeSitterService.ts +++ b/src/vs/editor/standalone/browser/standaloneTreeSitterService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { Parser } from '@vscode/tree-sitter-wasm'; +import type * as Parser from '@vscode/tree-sitter-wasm'; import { Event } from '../../../base/common/event.js'; import { ITextModel } from '../../common/model.js'; import { ITextModelTreeSitter, ITreeSitterParseResult, ITreeSitterParserService, TreeUpdateEvent } from '../../common/services/treeSitterParserService.js'; diff --git a/src/vs/editor/test/browser/services/treeSitterParserService.test.ts b/src/vs/editor/test/browser/services/treeSitterParserService.test.ts index 5475195b7db4..f234cc6973e6 100644 --- a/src/vs/editor/test/browser/services/treeSitterParserService.test.ts +++ b/src/vs/editor/test/browser/services/treeSitterParserService.test.ts @@ -4,48 +4,57 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; -import { TextModelTreeSitter, TreeSitterImporter, TreeSitterLanguages } from '../../../common/services/treeSitter/treeSitterParserService.js'; -import type { Parser } from '@vscode/tree-sitter-wasm'; +import { TextModelTreeSitter, TreeSitterLanguages } from '../../../common/services/treeSitter/treeSitterParserService.js'; +import type * as Parser from '@vscode/tree-sitter-wasm'; import { createTextModel } from '../../common/testTextModel.js'; import { timeout } from '../../../../base/common/async.js'; import { ConsoleMainLogger, ILogService } from '../../../../platform/log/common/log.js'; import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { LogService } from '../../../../platform/log/common/logService.js'; import { mock } from '../../../../base/test/common/mock.js'; +import { ITreeSitterImporter } from '../../../common/services/treeSitterParserService.js'; -class MockParser implements Parser { - static async init(): Promise { } +class MockParser implements Parser.Parser { + language: Parser.Language | null = null; delete(): void { } - parse(input: string | Parser.Input, oldTree?: Parser.Tree, options?: Parser.Options): Parser.Tree { + setLanguage(language: Parser.Language | null) { return this; } + parse(callback: string | Parser.ParseCallback, oldTree?: Parser.Tree | null, options?: Parser.ParseOptions): Parser.Tree | null { return new MockTree(); } + reset(): void { } getIncludedRanges(): Parser.Range[] { return []; } getTimeoutMicros(): number { return 0; } setTimeoutMicros(timeout: number): void { } - reset(): void { } - getLanguage(): Parser.Language { return {} as any; } - setLanguage(): void { } - getLogger(): Parser.Logger { + setLogger(callback: Parser.LogCallback | boolean | null): this { throw new Error('Method not implemented.'); } - setLogger(logFunc?: Parser.Logger | false | null): void { + getLogger(): Parser.LogCallback | null { throw new Error('Method not implemented.'); } } -class MockTreeSitterImporter extends TreeSitterImporter { - public override async getParserClass(): Promise { +class MockTreeSitterImporter implements ITreeSitterImporter { + _serviceBrand: undefined; + getParserClass(): Promise { return MockParser as any; } + getLanguageClass(): Promise { + return MockLanguage as any; + } + getQueryClass(): Promise { + throw new Error('Method not implemented.'); + } + } class MockTree implements Parser.Tree { + language: Parser.Language = new MockLanguage(); editorLanguage: string = ''; editorContents: string = ''; - rootNode: Parser.SyntaxNode = {} as any; - rootNodeWithOffset(offsetBytes: number, offsetExtent: Parser.Point): Parser.SyntaxNode { + rootNode: Parser.Node = {} as any; + rootNodeWithOffset(offsetBytes: number, offsetExtent: Parser.Point): Parser.Node { throw new Error('Method not implemented.'); } copy(): Parser.Tree { @@ -73,6 +82,23 @@ class MockTree implements Parser.Tree { } class MockLanguage implements Parser.Language { + types: string[] = []; + fields: (string | null)[] = []; + get name(): string | null { + throw new Error('Method not implemented.'); + } + get abiVersion(): number { + throw new Error('Method not implemented.'); + } + get metadata(): Parser.LanguageMetadata | null { + throw new Error('Method not implemented.'); + } + get supertypes(): number[] { + throw new Error('Method not implemented.'); + } + subtypes(supertype: number): number[] { + throw new Error('Method not implemented.'); + } version: number = 0; fieldCount: number = 0; stateCount: number = 0; @@ -101,14 +127,14 @@ class MockLanguage implements Parser.Language { query(source: string): Parser.Query { throw new Error('Method not implemented.'); } - lookaheadIterator(stateId: number): Parser.LookaheadIterable | null { + lookaheadIterator(stateId: number): Parser.LookaheadIterator | null { throw new Error('Method not implemented.'); } languageId: string = ''; } suite('TreeSitterParserService', function () { - const treeSitterImporter: TreeSitterImporter = new MockTreeSitterImporter(); + const treeSitterImporter: ITreeSitterImporter = new MockTreeSitterImporter(); let logService: ILogService; let telemetryService: ITelemetryService; setup(function () { diff --git a/src/vs/editor/test/common/services/testTreeSitterService.ts b/src/vs/editor/test/common/services/testTreeSitterService.ts index e2ca50462ecc..a41369c53e0c 100644 --- a/src/vs/editor/test/common/services/testTreeSitterService.ts +++ b/src/vs/editor/test/common/services/testTreeSitterService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { Parser } from '@vscode/tree-sitter-wasm'; +import type * as Parser from '@vscode/tree-sitter-wasm'; import { Event } from '../../../../base/common/event.js'; import { ITextModel } from '../../../common/model.js'; import { ITreeSitterParserService, ITreeSitterParseResult, ITextModelTreeSitter, TreeUpdateEvent } from '../../../common/services/treeSitterParserService.js'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index 4da76c311442..ff2710dcd2b2 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -32,7 +32,7 @@ import { SEMANTIC_HIGHLIGHTING_SETTING_ID, IEditorSemanticHighlightingOptions } import { Schemas } from '../../../../../base/common/network.js'; import { ILanguageFeaturesService } from '../../../../../editor/common/services/languageFeatures.js'; import { ITreeSitterParserService } from '../../../../../editor/common/services/treeSitterParserService.js'; -import type { Parser } from '@vscode/tree-sitter-wasm'; +import type * as Parser from '@vscode/tree-sitter-wasm'; const $ = dom.$; @@ -646,11 +646,11 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { return null; } - private _walkTreeforPosition(cursor: Parser.TreeCursor, pos: Position): Parser.SyntaxNode | null { + private _walkTreeforPosition(cursor: Parser.TreeCursor, pos: Position): Parser.Node | null { const offset = this._model.getOffsetAt(pos); cursor.gotoFirstChild(); let goChild: boolean = false; - let lastGoodNode: Parser.SyntaxNode | null = null; + let lastGoodNode: Parser.Node | null = null; do { if (cursor.currentNode.startIndex <= offset && offset < cursor.currentNode.endIndex) { goChild = true; @@ -662,7 +662,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { return lastGoodNode; } - private _getTreeSitterTokenAtPosition(tree: Parser.Tree, pos: Position): Parser.SyntaxNode | null { + private _getTreeSitterTokenAtPosition(tree: Parser.Tree, pos: Position): Parser.Node | null { const cursor = tree.walk(); return this._walkTreeforPosition(cursor, pos); diff --git a/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts index 57e6c944465b..496922ef3913 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from '../../../../base/common/uri.js'; -import type { Parser } from '@vscode/tree-sitter-wasm'; +import type * as Parser from '@vscode/tree-sitter-wasm'; import { ILanguageService } from '../../../../editor/common/languages/language.js'; import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; diff --git a/src/vs/workbench/services/treeSitter/browser/treeSitterTokenizationFeature.contribution.ts b/src/vs/workbench/services/treeSitter/browser/treeSitterTokenizationFeature.contribution.ts index 21701ec617c5..6c06c1b189c1 100644 --- a/src/vs/workbench/services/treeSitter/browser/treeSitterTokenizationFeature.contribution.ts +++ b/src/vs/workbench/services/treeSitter/browser/treeSitterTokenizationFeature.contribution.ts @@ -6,7 +6,7 @@ import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; import { IWorkbenchContribution, WorkbenchPhase, registerWorkbenchContribution2 } from '../../../common/contributions.js'; import { TreeSitterTextModelService } from '../../../../editor/common/services/treeSitter/treeSitterParserService.js'; -import { ITreeSitterParserService } from '../../../../editor/common/services/treeSitterParserService.js'; +import { ITreeSitterImporter, ITreeSitterParserService, TreeSitterImporter } from '../../../../editor/common/services/treeSitterParserService.js'; import { ITreeSitterTokenizationFeature } from './treeSitterTokenizationFeature.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { CommandsRegistry } from '../../../../platform/commands/common/commands.js'; @@ -28,6 +28,7 @@ class TreeSitterTokenizationInstantiator implements IWorkbenchContribution { ) { } } +registerSingleton(ITreeSitterImporter, TreeSitterImporter, InstantiationType.Eager); registerSingleton(ITreeSitterParserService, TreeSitterTextModelService, InstantiationType.Eager); registerWorkbenchContribution2(TreeSitterTokenizationInstantiator.ID, TreeSitterTokenizationInstantiator, WorkbenchPhase.BlockRestore); diff --git a/src/vs/workbench/services/treeSitter/browser/treeSitterTokenizationFeature.ts b/src/vs/workbench/services/treeSitter/browser/treeSitterTokenizationFeature.ts index 34bcb6b7536c..139cd64d5c33 100644 --- a/src/vs/workbench/services/treeSitter/browser/treeSitterTokenizationFeature.ts +++ b/src/vs/workbench/services/treeSitter/browser/treeSitterTokenizationFeature.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { Parser } from '@vscode/tree-sitter-wasm'; +import type * as Parser from '@vscode/tree-sitter-wasm'; import { Emitter, Event } from '../../../../base/common/event.js'; import { Disposable, DisposableMap, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; import { AppResourcePath, FileAccess } from '../../../../base/common/network.js'; import { ILanguageIdCodec, ITreeSitterTokenizationSupport, LazyTokenizationSupport, QueryCapture, TreeSitterTokenizationRegistry } from '../../../../editor/common/languages.js'; import { ITextModel } from '../../../../editor/common/model.js'; -import { EDITOR_EXPERIMENTAL_PREFER_TREESITTER, ITreeSitterParserService, ITreeSitterParseResult, TreeUpdateEvent, RangeChange } from '../../../../editor/common/services/treeSitterParserService.js'; +import { EDITOR_EXPERIMENTAL_PREFER_TREESITTER, ITreeSitterParserService, ITreeSitterParseResult, TreeUpdateEvent, RangeChange, ITreeSitterImporter } from '../../../../editor/common/services/treeSitterParserService.js'; import { IModelTokensChangedEvent } from '../../../../editor/common/textModelEvents.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IFileService } from '../../../../platform/files/common/files.js'; @@ -45,6 +45,7 @@ export class TreeSitterTokenizationFeature extends Disposable implements ITreeSi private readonly _tokenizersRegistrations: DisposableMap = this._register(new DisposableMap()); constructor( + @ITreeSitterImporter private readonly _treeSitterImporter: ITreeSitterImporter, @ILanguageService private readonly _languageService: ILanguageService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @@ -92,7 +93,8 @@ export class TreeSitterTokenizationFeature extends Disposable implements ITreeSi private async _createTokenizationSupport(languageId: string): Promise { const queries = await this._fetchQueries(languageId); - return this._instantiationService.createInstance(TreeSitterTokenizationSupport, queries, languageId, this._languageService.languageIdCodec); + const Query = await this._treeSitterImporter.getQueryClass(); + return this._instantiationService.createInstance(TreeSitterTokenizationSupport, queries, Query, languageId, this._languageService.languageIdCodec); } } @@ -105,6 +107,7 @@ export class TreeSitterTokenizationSupport extends Disposable implements ITreeSi constructor( private readonly _queries: TreeSitterQueries, + private readonly Query: typeof Parser.Query, private readonly _languageId: string, private readonly _languageIdCodec: ILanguageIdCodec, @ITreeSitterParserService private readonly _treeSitterService: ITreeSitterParserService, @@ -314,12 +317,12 @@ export class TreeSitterTokenizationSupport extends Disposable implements ITreeSi if (!language) { if (!this._languageAddedListener) { this._languageAddedListener = this._register(Event.onceIf(this._treeSitterService.onDidAddLanguage, e => e.id === this._languageId)((e) => { - this._query = e.language.query(this._queries); + this._query = new this.Query(e.language, this._queries); })); } return; } - this._query = language.query(this._queries); + this._query = new this.Query(language, this._queries); } return this._query; } diff --git a/src/vs/workbench/test/electron-main/treeSitterTokenizationFeature.test.ts b/src/vs/workbench/test/electron-main/treeSitterTokenizationFeature.test.ts index 1eab214ca26a..c246b1e8568a 100644 --- a/src/vs/workbench/test/electron-main/treeSitterTokenizationFeature.test.ts +++ b/src/vs/workbench/test/electron-main/treeSitterTokenizationFeature.test.ts @@ -20,7 +20,7 @@ import { IEnvironmentService } from '../../../platform/environment/common/enviro import { ModelService } from '../../../editor/common/services/modelService.js'; // eslint-disable-next-line local/code-layering, local/code-import-patterns import { TreeSitterTokenizationFeature } from '../../services/treeSitter/browser/treeSitterTokenizationFeature.js'; -import { ITreeSitterParserService, TreeUpdateEvent } from '../../../editor/common/services/treeSitterParserService.js'; +import { ITreeSitterImporter, ITreeSitterParserService, TreeSitterImporter, TreeUpdateEvent } from '../../../editor/common/services/treeSitterParserService.js'; import { ITreeSitterTokenizationSupport, TreeSitterTokenizationRegistry } from '../../../editor/common/languages.js'; import { FileService } from '../../../platform/files/common/fileService.js'; import { Schemas } from '../../../base/common/network.js'; @@ -131,6 +131,7 @@ suite('Tree Sitter TokenizationFeature', function () { instantiationService.set(ITextResourcePropertiesService, textResourcePropertiesService); languageConfigurationService = disposables.add(instantiationService.createInstance(TestLanguageConfigurationService)); instantiationService.set(ILanguageConfigurationService, languageConfigurationService); + instantiationService.set(ITreeSitterImporter, instantiationService.createInstance(TreeSitterImporter)); fileService = disposables.add(instantiationService.createInstance(FileService)); const diskFileSystemProvider = disposables.add(new DiskFileSystemProvider(logService)); From 2035725aca7c3a4961f60cb894488875015dd6c4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 5 Feb 2025 10:50:04 -0800 Subject: [PATCH 0401/2632] Add generic fig tests Part of #239515 --- .../terminal-suggest/src/test/fig.test.ts | 133 ++++++++++++++++++ .../src/test/terminalSuggestMain.test.ts | 3 + 2 files changed, 136 insertions(+) create mode 100644 extensions/terminal-suggest/src/test/fig.test.ts diff --git a/extensions/terminal-suggest/src/test/fig.test.ts b/extensions/terminal-suggest/src/test/fig.test.ts new file mode 100644 index 000000000000..d6c6b2682849 --- /dev/null +++ b/extensions/terminal-suggest/src/test/fig.test.ts @@ -0,0 +1,133 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { testPaths, type ISuiteSpec } from './helpers'; + +export const figGenericTestSuites: ISuiteSpec[] = [ + { + name: 'Fig name and description only', + completionSpecs: [ + { + name: 'foo', + description: 'Foo', + } + ], + availableCommands: 'foo', + testSpecs: [ + // Typing a path + { input: '|', expectedCompletions: ['foo'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'f|', expectedCompletions: ['foo'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'fo|', expectedCompletions: ['foo'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + { input: 'foo|', expectedCompletions: ['foo'], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + + // Basic arguments (fallback) + { input: 'foo |', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } } + ] + }, + { + name: 'Fig top-level args files only', + completionSpecs: [ + { + name: 'foo', + description: 'Foo', + args: { + template: 'filepaths', + isVariadic: true, + } + } + ], + availableCommands: 'foo', + testSpecs: [ + { input: 'foo |', expectedCompletions: [], expectedResourceRequests: { type: 'files', cwd: testPaths.cwd } }, + ] + }, + { + name: 'Fig top-level args folders only', + completionSpecs: [ + { + name: 'foo', + description: 'Foo', + args: { + template: 'folders', + isVariadic: true, + } + } + ], + availableCommands: 'foo', + testSpecs: [ + { input: 'foo |', expectedCompletions: [], expectedResourceRequests: { type: 'folders', cwd: testPaths.cwd } }, + ] + }, + { + name: 'Fig top-level args files and folders', + completionSpecs: [ + { + name: 'foo', + description: 'Foo', + args: { + template: ['filepaths', 'folders'], + isVariadic: true, + } + } + ], + availableCommands: 'foo', + testSpecs: [ + { input: 'foo |', expectedCompletions: [], expectedResourceRequests: { type: 'both', cwd: testPaths.cwd } }, + ] + }, + { + name: 'Fig top-level options', + completionSpecs: [ + { + name: 'foo', + description: 'Foo', + options: [ + { name: '--bar', description: 'Bar' }, + { name: '--baz', description: 'Baz' } + ] + } + ], + availableCommands: 'foo', + testSpecs: [ + { input: 'foo |', expectedCompletions: ['--bar', '--baz'] }, + { input: 'foo bar|', expectedCompletions: ['--bar', '--baz'] }, + // TODO: Duplicate options should not be presented https://github.com/microsoft/vscode/issues/239607 + // { input: 'foo --bar |', expectedCompletions: ['--baz'] }, + // { input: 'foo --baz |', expectedCompletions: ['--bar'] }, + ] + }, + { + name: 'Fig top-level option values', + completionSpecs: [ + { + name: 'foo', + description: 'Foo', + options: [ + { + name: '--bar', + description: 'Bar', + args: { + name: 'baz', + suggestions: [ + 'a', + 'b', + 'c', + ], + } + } + ] + } + ], + availableCommands: 'foo', + testSpecs: [ + { input: 'foo |', expectedCompletions: ['--bar'] }, + { input: 'foo --bar |', expectedCompletions: ['a', 'b', 'c'] }, + // TODO: All options should be suggested here? https://github.com/microsoft/vscode/issues/239713 + // { input: 'foo --bar a|', expectedCompletions: ['a', 'b', 'c'] }, + // { input: 'foo --bar b|', expectedCompletions: ['a', 'b', 'c'] }, + // { input: 'foo --bar c|', expectedCompletions: ['a', 'b', 'c'] }, + ] + } +]; diff --git a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts index 189a022101b7..58c07c7a9c10 100644 --- a/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts +++ b/extensions/terminal-suggest/src/test/terminalSuggestMain.test.ts @@ -20,6 +20,7 @@ import { rmdirTestSuiteSpec } from './completions/upstream/rmdir.test'; import { touchTestSuiteSpec } from './completions/upstream/touch.test'; import { osIsWindows } from '../helpers/os'; import codeCompletionSpec from '../completions/code'; +import { figGenericTestSuites } from './fig.test'; const testSpecs2: ISuiteSpec[] = [ { @@ -34,6 +35,8 @@ const testSpecs2: ISuiteSpec[] = [ ] }, + ...figGenericTestSuites, + // completions/ cdTestSuite, codeTestSuite, From cd52101a4b2b05bd8845a75eb0a54a269f1c0efb Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 5 Feb 2025 11:09:18 -0800 Subject: [PATCH 0402/2632] Register terminal variables from vscode (#239653) * Register terminal variables from vscode * Simplify terminalLastCommand * Bump chatParticipantPrivate version just to ensure that moving variables from the extension to core doesn't lead to dupes or missing vars --- .../common/extensionsApiProposals.ts | 2 +- .../contrib/chat/browser/chat.contribution.ts | 2 + .../browser/contrib/chatInputCompletions.ts | 2 - .../chat/browser/variables/variables.ts | 90 +++++++++++++++++++ ...scode.proposed.chatParticipantPrivate.d.ts | 2 +- 5 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/variables/variables.ts diff --git a/src/vs/platform/extensions/common/extensionsApiProposals.ts b/src/vs/platform/extensions/common/extensionsApiProposals.ts index 79fc43225b96..bf5936b2a707 100644 --- a/src/vs/platform/extensions/common/extensionsApiProposals.ts +++ b/src/vs/platform/extensions/common/extensionsApiProposals.ts @@ -33,7 +33,7 @@ const _allApiProposals = { }, chatParticipantPrivate: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts', - version: 2 + version: 3 }, chatProvider: { proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatProvider.d.ts', diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 47c41d56362b..bb2b7f1e6e91 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -91,6 +91,7 @@ import { IContextKeyService } from '../../../../platform/contextkey/common/conte import { ChatContextKeys } from '../common/chatContextKeys.js'; import { IPromptSyntaxService } from '../common/promptSyntax/service/types.js'; import { PromptSyntaxService } from '../common/promptSyntax/service/promptSyntaxService.js'; +import { BuiltinVariablesContribution } from './variables/variables.js'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -392,6 +393,7 @@ registerWorkbenchContribution2(ChatGettingStartedContribution.ID, ChatGettingSta registerWorkbenchContribution2(ChatSetupContribution.ID, ChatSetupContribution, WorkbenchPhase.BlockRestore); registerWorkbenchContribution2(ChatQuotasStatusBarEntry.ID, ChatQuotasStatusBarEntry, WorkbenchPhase.Eventually); registerWorkbenchContribution2(BuiltinToolsContribution.ID, BuiltinToolsContribution, WorkbenchPhase.Eventually); +registerWorkbenchContribution2(BuiltinVariablesContribution.ID, BuiltinVariablesContribution, WorkbenchPhase.Eventually); registerWorkbenchContribution2(ChatAgentSettingContribution.ID, ChatAgentSettingContribution, WorkbenchPhase.BlockRestore); registerChatActions(); diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts index 9ab466c60a44..4e3fc6dd1e7d 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts @@ -22,7 +22,6 @@ import { IOutlineModelService } from '../../../../../editor/contrib/documentSymb import { localize } from '../../../../../nls.js'; import { Action2, registerAction2 } from '../../../../../platform/actions/common/actions.js'; import { CommandsRegistry } from '../../../../../platform/commands/common/commands.js'; -import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js'; import { ILabelService } from '../../../../../platform/label/common/label.js'; import { Registry } from '../../../../../platform/registry/common/platform.js'; @@ -835,7 +834,6 @@ class VariableCompletions extends Disposable { @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, @IChatVariablesService private readonly chatVariablesService: IChatVariablesService, - @IConfigurationService configService: IConfigurationService, @ILanguageModelToolsService toolsService: ILanguageModelToolsService ) { super(); diff --git a/src/vs/workbench/contrib/chat/browser/variables/variables.ts b/src/vs/workbench/contrib/chat/browser/variables/variables.ts new file mode 100644 index 000000000000..e2e59860f300 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/variables/variables.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from '../../../../../base/common/cancellation.js'; +import { Codicon } from '../../../../../base/common/codicons.js'; +import { Disposable } from '../../../../../base/common/lifecycle.js'; +import { ProviderResult } from '../../../../../editor/common/languages.js'; +import { localize } from '../../../../../nls.js'; +import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { TerminalCapability } from '../../../../../platform/terminal/common/capabilities/capabilities.js'; +import { IWorkbenchContribution } from '../../../../common/contributions.js'; +import { ITerminalService } from '../../../terminal/browser/terminal.js'; +import { IChatModel } from '../../common/chatModel.js'; +import { IChatRequestVariableValue, IChatVariableResolverProgress, IChatVariablesService } from '../../common/chatVariables.js'; + +export class BuiltinVariablesContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'chat.builtinVariables'; + + constructor( + @IChatVariablesService varsService: IChatVariablesService, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(); + + const terminalSelectionVar = instantiationService.createInstance(TerminalSelectionVariable); + varsService.registerVariable({ + id: 'copilot.terminalSelection', + name: 'terminalSelection', + fullName: localize('termSelection', "Terminal Selection"), + description: localize('termSelectionDescription', "The active terminal's selection"), + icon: Codicon.terminal, + }, async (messageText: string, arg: string | undefined, model: IChatModel, progress: (part: IChatVariableResolverProgress) => void, token: CancellationToken): Promise => { + return await terminalSelectionVar.resolve(token); + }); + + const terminalLastCommandVar = instantiationService.createInstance(TerminalLastCommandVariable); + varsService.registerVariable({ + id: 'copilot.terminalLastCommand', + name: 'terminalLastCommand', + fullName: localize('terminalLastCommand', "Terminal Last Command"), + description: localize('termLastCommandDesc', "The active terminal's last run command"), + icon: Codicon.terminal, + }, async (messageText: string, arg: string | undefined, model: IChatModel, progress: (part: IChatVariableResolverProgress) => void, token: CancellationToken): Promise => { + return await terminalLastCommandVar.resolve(token); + }); + } +} + +class TerminalSelectionVariable { + constructor( + @ITerminalService private readonly terminalService: ITerminalService + ) { } + + resolve(token: CancellationToken): ProviderResult { + const terminalSelection = this.terminalService.activeInstance?.selection; + return terminalSelection ? `The active terminal's selection:\n${terminalSelection}` : undefined; + } +} + +class TerminalLastCommandVariable { + constructor( + @ITerminalService private readonly terminalService: ITerminalService + ) { } + + resolve(token: CancellationToken): ProviderResult { + const lastCommand = this.terminalService.activeInstance?.capabilities.get(TerminalCapability.CommandDetection)?.commands.at(-1); + if (!lastCommand) { + return; + } + + const userPrompt: string[] = []; + userPrompt.push(`The following is the last command run in the terminal:`); + userPrompt.push(lastCommand.command); + if (lastCommand.cwd) { + userPrompt.push(`It was run in the directory:`); + userPrompt.push(lastCommand.cwd); + } + const output = lastCommand.getOutput(); + if (output) { + userPrompt.push(`It has the following output:`); + userPrompt.push(output); + } + + const prompt = userPrompt.join('\n'); + return `The active terminal's last run command:\n${prompt}`; + } +} diff --git a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts index 0aff2d2091b1..10a4ce36ce52 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// version: 2 +// version: 3 declare module 'vscode' { From c77b0b46a8e4081030b62e82aab669fb878a5568 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 6 Feb 2025 06:17:38 +1100 Subject: [PATCH 0403/2632] Remove assumption there's just one editing session (#239711) --- .../contrib/chatEdit/notebookCellDecorators.ts | 12 +++++------- .../chatEdit/notebookChatActionsOverlay.ts | 8 ++++++-- .../chatEdit/notebookChatEditController.ts | 6 +++--- .../contrib/chatEdit/notebookSynchronizer.ts | 7 ++----- .../chatEdit/notebookSynchronizerService.ts | 16 ++++++---------- 5 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookCellDecorators.ts b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookCellDecorators.ts index f7647dda6a75..50bfee22f313 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookCellDecorators.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookCellDecorators.ts @@ -86,15 +86,13 @@ export class NotebookCellDiffDecorator extends DisposableStore { })); const shouldBeReadOnly = derived(this, r => { - const editor = editorObs.read(r); - if (!editor) { - return false; - } - const value = this._chatEditingService.globalEditingSessionObs.read(r); - if (!value || value.state.read(r) !== ChatEditingSessionState.StreamingEdits) { + const editorUri = editorObs.read(r)?.getModel()?.uri; + if (!editorUri) { return false; } - return value.entries.read(r).some(e => isEqual(e.modifiedURI, editor.getModel()?.uri)); + const sessions = this._chatEditingService.editingSessionsObs.read(r); + const session = sessions.find(s => s.entries.read(r).some(e => isEqual(e.modifiedURI, editorUri))); + return session?.state.read(r) === ChatEditingSessionState.StreamingEdits; }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts index f9c9ed3a545f..33bc1f15d1d4 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatActionsOverlay.ts @@ -33,9 +33,13 @@ export class NotebookChatActionsOverlayController extends Disposable { const notebookModel = observableFromEvent(this.notebookEditor.onDidChangeModel, e => e); this._register(autorunWithStore((r, store) => { - const session = this._chatEditingService.globalEditingSessionObs.read(r); const model = notebookModel.read(r); - if (!model || !session) { + if (!model) { + return; + } + const sessions = this._chatEditingService.editingSessionsObs.read(r); + const session = sessions.find(s => s.readEntry(model.uri, r)); + if (!session) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatEditController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatEditController.ts index c6d9db8501f0..b62807826523 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatEditController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookChatEditController.ts @@ -80,12 +80,12 @@ class NotebookChatEditorController extends Disposable { let notebookSynchronizer: IReference; const entryObs = derived((r) => { - const session = this._chatEditingService.globalEditingSessionObs.read(r); const model = notebookModel.read(r); - if (!model || !session) { + if (!model) { return; } - return session.readEntry(model.uri, r); + const sessions = this._chatEditingService.editingSessionsObs.read(r); + return sessions.map(s => s.readEntry(model.uri, r)).find(r => !!r); }).recomputeInitiallyAndOnChange(this._store); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizer.ts b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizer.ts index 2c376b3b0309..b22e053cad73 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizer.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizer.ts @@ -91,11 +91,8 @@ export class NotebookModelSynchronizer extends Disposable { super(); const entryObs = derived((r) => { - const session = _chatEditingService.globalEditingSessionObs.read(r); - if (!session) { - return; - } - return session.readEntry(model.uri, r); + const sessions = _chatEditingService.editingSessionsObs.read(r); + return sessions.map(s => s.readEntry(model.uri, r)).find(r => !!r); }).recomputeInitiallyAndOnChange(this._store); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizerService.ts b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizerService.ts index 8f93143cccd0..d42aa1b1230a 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizerService.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/chatEdit/notebookSynchronizerService.ts @@ -28,8 +28,8 @@ class NotebookSynchronizerSaveParticipant extends NotebookSaveParticipant { } override async participate(workingCopy: IStoredFileWorkingCopy, context: IStoredFileWorkingCopySaveParticipantContext, progress: IProgress, token: CancellationToken): Promise { - const session = this._chatEditingService.globalEditingSessionObs.get(); - + const sessions = this._chatEditingService.editingSessionsObs.get(); + const session = sessions.find(s => s.getEntry(workingCopy.resource)); if (!session) { return; } @@ -74,14 +74,10 @@ export class NotebookSynchronizerService extends Disposable implements INotebook async revert(workingCopy: IStoredFileWorkingCopy | IUntitledFileWorkingCopy) { // check if we have mirror document const resource = workingCopy.resource; - - const session = this._chatEditingService.globalEditingSessionObs.get(); - - if (session) { - const entry = session.getEntry(resource); - if (entry instanceof ChatEditingModifiedNotebookEntry) { - await entry.revertMirrorDocument(); - } + const sessions = this._chatEditingService.editingSessionsObs.get(); + const entry = sessions.map(s => s.getEntry(resource)).find(r => !!r); + if (entry instanceof ChatEditingModifiedNotebookEntry) { + await entry.revertMirrorDocument(); } } } From 9f448306f040600e5c8ee6b2f833d1a6412aceca Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Wed, 5 Feb 2025 11:19:06 -0800 Subject: [PATCH 0404/2632] Hide vertical scrollbar in Getting Started pages and adjust video element attributes (#239716) --- .../contrib/welcomeGettingStarted/browser/gettingStarted.ts | 5 +++-- .../browser/gettingStartedDetailsRenderer.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 6346f20f11f9..39313994cd0d 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -72,6 +72,7 @@ import { GettingStartedIndexList } from './gettingStartedList.js'; import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js'; import { AccessibleViewAction } from '../../accessibility/browser/accessibleViewActions.js'; import { KeybindingLabel } from '../../../../base/browser/ui/keybindingLabel/keybindingLabel.js'; +import { ScrollbarVisibility } from '../../../../base/common/scrollable.js'; const SLIDE_TRANSITION_TIME_MS = 250; const configurationKey = 'workbench.startupEditor'; @@ -820,8 +821,8 @@ export class GettingStartedPage extends EditorPane { this.stepsContent = $('.gettingStartedDetailsContent', {}); - this.detailsPageScrollbar = this._register(new DomScrollableElement(this.stepsContent, { className: 'full-height-scrollable' })); - this.categoriesPageScrollbar = this._register(new DomScrollableElement(this.categoriesSlide, { className: 'full-height-scrollable categoriesScrollbar' })); + this.detailsPageScrollbar = this._register(new DomScrollableElement(this.stepsContent, { className: 'full-height-scrollable', vertical: ScrollbarVisibility.Hidden })); + this.categoriesPageScrollbar = this._register(new DomScrollableElement(this.categoriesSlide, { className: 'full-height-scrollable categoriesScrollbar', vertical: ScrollbarVisibility.Hidden })); this.stepsSlide.appendChild(this.detailsPageScrollbar.getDomNode()); diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts index d2be9cd03095..9490776109bd 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer.ts @@ -218,7 +218,7 @@ export class GettingStartedDetailsRenderer { -