diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 6a3f89dc47..e9a4f27963 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -50,10 +50,6 @@ "name": "vs/workbench/contrib/comments", "project": "vscode-workbench" }, - { - "name": "vs/workbench/contrib/testCustomEditors", - "project": "vscode-workbench" - }, { "name": "vs/workbench/contrib/debug", "project": "vscode-workbench" diff --git a/extensions/configuration-editing/schemas/attachContainer.schema.json b/extensions/configuration-editing/schemas/attachContainer.schema.json index ba0df4b33d..cebd0f2bc3 100644 --- a/extensions/configuration-editing/schemas/attachContainer.schema.json +++ b/extensions/configuration-editing/schemas/attachContainer.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "description": "Configures an attached to container", "allowComments": true, "type": "object", @@ -18,6 +18,10 @@ "type": "integer" } }, + "settings": { + "$ref": "vscode://schemas/settings/machine", + "description": "Machine specific settings that should be copied into the container." + }, "remoteEnv": { "type": "object", "additionalProperties": { diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json index 631c58e82b..aed58455d0 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "description": "Defines a dev container", "allowComments": true, "type": "object", @@ -84,6 +84,13 @@ "type": "boolean", "description": "Controls whether on Linux the container's user should be updated with the local user's UID and GID. On by default." }, + "mounts": { + "type": "array", + "description": "Mount points to set up when creating the container. See Docker's documentation for the --mount option for the supported syntax.", + "items": { + "type": "string" + } + }, "runArgs": { "type": "array", "description": "The arguments required when starting in the container.", diff --git a/extensions/image-preview/README.md b/extensions/image-preview/README.md index ccb5ac3954..d3f0bd6cb6 100644 --- a/extensions/image-preview/README.md +++ b/extensions/image-preview/README.md @@ -13,5 +13,4 @@ Supported image formats: - `*.bmp` - `*.gif` - `*.ico` -- `*.tga` - `*.webp` diff --git a/extensions/image-preview/package.json b/extensions/image-preview/package.json index 8be8486a54..a0664f98e3 100644 --- a/extensions/image-preview/package.json +++ b/extensions/image-preview/package.json @@ -2,7 +2,7 @@ "name": "image-preview", "displayName": "%displayName%", "description": "%description%", - "extensionKind": "ui", + "extensionKind": ["ui", "workspace"], "version": "1.0.0", "publisher": "vscode", "icon": "icon.png", diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index c51dbb7c0c..96f55ae1c5 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -10,8 +10,16 @@ import { xhr, XHRResponse, getErrorStatusDescription } from 'request-light'; const localize = nls.loadMessageBundle(); -import { workspace, window, languages, commands, ExtensionContext, extensions, Uri, LanguageConfiguration, Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, ProviderResult, TextEdit, Range, Disposable } from 'vscode'; -import { LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType, DidChangeConfigurationNotification, HandleDiagnosticsSignature, ResponseError, DocumentRangeFormattingParams, DocumentRangeFormattingRequest } from 'vscode-languageclient'; +import { + workspace, window, languages, commands, ExtensionContext, extensions, Uri, LanguageConfiguration, + Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, + ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext +} from 'vscode'; +import { + LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType, + DidChangeConfigurationNotification, HandleDiagnosticsSignature, ResponseError, DocumentRangeFormattingParams, + DocumentRangeFormattingRequest, ProvideCompletionItemsSignature +} from 'vscode-languageclient'; import TelemetryReporter from 'vscode-extension-telemetry'; import { hash } from './utils/hash'; @@ -36,6 +44,10 @@ namespace SchemaAssociationNotification { export const type: NotificationType = new NotificationType('json/schemaAssociations'); } +namespace ResultLimitReachedNotification { + export const type: NotificationType = new NotificationType('json/resultLimitReached'); +} + interface IPackageInfo { name: string; version: string; @@ -132,6 +144,29 @@ export function activate(context: ExtensionContext) { } next(uri, diagnostics); + }, + // testing the replace / insert mode + provideCompletionItem(document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature): ProviderResult { + function updateRanges(item: CompletionItem) { + const range = item.range; + if (range && range.end.isAfter(position) && range.start.isBeforeOrEqual(position)) { + item.range2 = { inserting: new Range(range.start, position), replacing: range }; + item.range = undefined; + } + } + function updateProposals(r: CompletionItem[] | CompletionList | null | undefined): CompletionItem[] | CompletionList | null | undefined { + if (r) { + (Array.isArray(r) ? r : r.items).forEach(updateRanges); + } + return r; + } + const isThenable = (obj: ProviderResult): obj is Thenable => obj && (obj)['then']; + + const r = next(document, position, context, token); + if (isThenable(r)) { + return r.then(updateProposals); + } + return updateProposals(r); } } }; @@ -239,6 +274,12 @@ export function activate(context: ExtensionContext) { updateFormatterRegistration(); toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() }); toDispose.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration('html.format.enable') && updateFormatterRegistration())); + + + client.onNotification(ResultLimitReachedNotification.type, message => { + window.showInformationMessage(`${message}\nUse setting 'json.maxItemsComputed' to configure the limit.`); + }); + }); let languageConfiguration: LanguageConfiguration = { @@ -320,6 +361,8 @@ function getSchemaAssociation(_context: ExtensionContext): ISchemaAssociations { function getSettings(): Settings { let httpSettings = workspace.getConfiguration('http'); + let resultLimit: number = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get('json.maxItemsComputed')))) || 5000; + let settings: Settings = { http: { proxy: httpSettings.get('proxy'), @@ -327,7 +370,7 @@ function getSettings(): Settings { }, json: { schemas: [], - resultLimit: 5000 + resultLimit } }; let schemaSettingsById: { [schemaId: string]: JSONSchemaSettings } = Object.create(null); @@ -422,5 +465,4 @@ function readJSONFile(location: string) { console.log(`Problems reading ${location}: ${e}`); return {}; } - } diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 833059f3c8..09f7c6c164 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -1,113 +1,125 @@ { - "name": "json-language-features", - "displayName": "%displayName%", - "description": "%description%", - "version": "1.0.0", - "publisher": "vscode", - "license": "MIT", - "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", - "engines": { - "vscode": "0.10.x" - }, - "icon": "icons/json.png", - "activationEvents": [ - "onLanguage:json", - "onLanguage:jsonc" - ], - "main": "./client/out/jsonMain", - "enableProposedApi": true, - "scripts": { - "compile": "gulp compile-extension:json-language-features-client compile-extension:json-language-features-server", - "watch": "gulp watch-extension:json-language-features-client watch-extension:json-language-features-server", - "postinstall": "cd server && yarn install", - "install-client-next": "yarn add vscode-languageclient@next" - }, - "categories": [ - "Programming Languages" - ], - "contributes": { - "configuration": { - "id": "json", - "order": 20, - "type": "object", - "title": "JSON", - "properties": { - "json.schemas": { - "type": "array", - "scope": "resource", - "description": "%json.schemas.desc%", - "items": { - "type": "object", - "default": { - "fileMatch": [ - "/myfile" - ], - "url": "schemaURL" - }, - "properties": { - "url": { - "type": "string", - "default": "/user.schema.json", - "description": "%json.schemas.url.desc%" - }, - "fileMatch": { - "type": "array", - "items": { - "type": "string", - "default": "MyFile.json", - "description": "%json.schemas.fileMatch.item.desc%" - }, - "minItems": 1, - "description": "%json.schemas.fileMatch.desc%" - }, - "schema": { - "$ref": "http://json-schema.org/draft-07/schema#", - "description": "%json.schemas.schema.desc%" - } - } - } - }, - "json.format.enable": { - "type": "boolean", - "scope": "window", - "default": true, - "description": "%json.format.enable.desc%" - }, - "json.trace.server": { - "type": "string", - "scope": "window", - "enum": [ - "off", - "messages", - "verbose" - ], - "default": "off", - "description": "%json.tracing.desc%" - }, - "json.colorDecorators.enable": { - "type": "boolean", - "scope": "window", - "default": true, - "description": "%json.colorDecorators.enable.desc%", - "deprecationMessage": "%json.colorDecorators.enable.deprecationMessage%" - } - } + "name": "json-language-features", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "publisher": "vscode", + "license": "MIT", + "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", + "engines": { + "vscode": "0.10.x" }, - "configurationDefaults": { - "[json]": { - "editor.quickSuggestions": { - "strings": true + "icon": "icons/json.png", + "activationEvents": [ + "onLanguage:json", + "onLanguage:jsonc" + ], + "main": "./client/out/jsonMain", + "enableProposedApi": true, + "scripts": { + "compile": "gulp compile-extension:json-language-features-client compile-extension:json-language-features-server", + "watch": "gulp watch-extension:json-language-features-client watch-extension:json-language-features-server", + "postinstall": "cd server && yarn install", + "install-client-next": "yarn add vscode-languageclient@next" + }, + "categories": [ + "Programming Languages" + ], + "contributes": { + "configuration": { + "id": "json", + "order": 20, + "type": "object", + "title": "JSON", + "properties": { + "json.schemas": { + "type": "array", + "scope": "resource", + "description": "%json.schemas.desc%", + "items": { + "type": "object", + "default": { + "fileMatch": [ + "/myfile" + ], + "url": "schemaURL" + }, + "properties": { + "url": { + "type": "string", + "default": "/user.schema.json", + "description": "%json.schemas.url.desc%" + }, + "fileMatch": { + "type": "array", + "items": { + "type": "string", + "default": "MyFile.json", + "description": "%json.schemas.fileMatch.item.desc%" + }, + "minItems": 1, + "description": "%json.schemas.fileMatch.desc%" + }, + "schema": { + "$ref": "http://json-schema.org/draft-07/schema#", + "description": "%json.schemas.schema.desc%" + } + } + } + }, + "json.format.enable": { + "type": "boolean", + "scope": "window", + "default": true, + "description": "%json.format.enable.desc%" + }, + "json.trace.server": { + "type": "string", + "scope": "window", + "enum": [ + "off", + "messages", + "verbose" + ], + "default": "off", + "description": "%json.tracing.desc%" + }, + "json.colorDecorators.enable": { + "type": "boolean", + "scope": "window", + "default": true, + "description": "%json.colorDecorators.enable.desc%", + "deprecationMessage": "%json.colorDecorators.enable.deprecationMessage%" + }, + "json.maxItemsComputed": { + "type": "number", + "default": 5000, + "description": "%json.maxItemsComputed.desc%" + } + } + }, + "configurationDefaults": { + "[json]": { + "editor.quickSuggestions": { + "strings": true + }, + "editor.suggest.insertMode": "replace" + }, + "[jsonc]": { + "editor.quickSuggestions": { + "strings": true + }, + "editor.suggest.insertMode": "replace" + } } - } + }, + "dependencies": { + "request-light": "^0.2.5", + "vscode-extension-telemetry": "0.1.1", + "vscode-languageclient": "^6.0.0-next.3", + "vscode-nls": "^4.1.1" + }, + "devDependencies": { + "@types/node": "^12.11.7" } - }, - "dependencies": { - "request-light": "^0.2.5", - "vscode-extension-telemetry": "0.1.1", - "vscode-languageclient": "^6.0.0-next.3", - "vscode-nls": "^4.1.1" - }, - "devDependencies": { - "@types/node": "^12.11.7" - } } diff --git a/extensions/json-language-features/package.nls.json b/extensions/json-language-features/package.nls.json index 9fae23da96..6b692bdbb2 100644 --- a/extensions/json-language-features/package.nls.json +++ b/extensions/json-language-features/package.nls.json @@ -11,5 +11,6 @@ "json.colorDecorators.enable.desc": "Enables or disables color decorators", "json.colorDecorators.enable.deprecationMessage": "The setting `json.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`.", "json.schemaResolutionErrorMessage": "Unable to resolve schema.", - "json.clickToRetry": "Click to retry." + "json.clickToRetry": "Click to retry.", + "json.maxItemsComputed.desc": "The maximum number of outline symbols and folding regions computed (limited for performance reasons)." } diff --git a/extensions/json-language-features/server/README.md b/extensions/json-language-features/server/README.md index 71356e3c44..66fd8437d6 100644 --- a/extensions/json-language-features/server/README.md +++ b/extensions/json-language-features/server/README.md @@ -63,6 +63,7 @@ The server supports the following settings: - `fileMatch`: an array of file names or paths (separated by `/`). `*` can be used as a wildcard. - `url`: The URL of the schema, optional when also a schema is provided. - `schema`: The schema content. + - `resultLimit`: The max number foldig ranges and otline symbols to be computed (for performance reasons) ```json { @@ -153,6 +154,16 @@ Notification: - method: 'json/schemaContent' - params: `string` the URL of the schema that has changed. +### Item Limit + +If the setting `resultLimit` is set, the JSON language server will limit the number of folding ranges and document symbols computed. +When the limit is reached, a notification `json/resultLimitReached` is sent that can be shown that camn be shown to the user. + +Notification: +- method: 'json/resultLimitReached' +- params: a human readable string to show to the user. + + ## Try The JSON language server is shipped with [Visual Studio Code](https://code.visualstudio.com/) as part of the built-in VSCode extension `json-language-features`. The server is started when the first JSON file is opened. The [VSCode JSON documentation](https://code.visualstudio.com/docs/languages/json) for detailed information on the user experience and has more information on how to configure the language support. diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 6dc132cc74..64a9c5f5eb 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -14,7 +14,7 @@ "dependencies": { "jsonc-parser": "^2.2.0", "request-light": "^0.2.5", - "vscode-json-languageservice": "^3.4.7", + "vscode-json-languageservice": "^3.4.9", "vscode-languageserver": "^6.0.0-next.3", "vscode-uri": "^2.1.1" }, diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index 183ab87c8a..f1a91c6804 100644 --- a/extensions/json-language-features/server/src/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/jsonServerMain.ts @@ -35,6 +35,10 @@ namespace SchemaContentChangeNotification { export const type: NotificationType = new NotificationType('json/schemaContent'); } +namespace ResultLimitReachedNotification { + export const type: NotificationType = new NotificationType('json/resultLimitReached'); +} + namespace ForceValidateRequest { export const type: RequestType = new RequestType('json/validate'); } @@ -211,7 +215,7 @@ namespace LimitExceededWarnings { } else { warning = { features: { [name]: name } }; warning.timeout = setTimeout(() => { - connection.window.showInformationMessage(`${posix.basename(uri)}: For performance reasons, ${Object.keys(warning.features).join(' and ')} have been limited to ${resultLimit} items.`); + connection.sendNotification(ResultLimitReachedNotification.type, `${posix.basename(uri)}: For performance reasons, ${Object.keys(warning.features).join(' and ')} have been limited to ${resultLimit} items.`); warning.timeout = undefined; }, 2000); pendingWarnings[uri] = warning; diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index f0fde79ab0..7ebc93341c 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -80,10 +80,10 @@ request-light@^0.2.5: https-proxy-agent "^2.2.3" vscode-nls "^4.1.1" -vscode-json-languageservice@^3.4.7: - version "3.4.7" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.4.7.tgz#8d85f3c1d46a1e58e9867d747552fb8c83d934fd" - integrity sha512-y3MN2+/yph3yoIHGmHu4ScYpm285L58XVvfGkd49xTQzLja4apxSbwzsYcP9QsqS0W7KuvoyiPhqksiudoMwjg== +vscode-json-languageservice@^3.4.9: + version "3.4.9" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.4.9.tgz#7ce485bb0f9a07b4d879c988baac9be2222909ad" + integrity sha512-4VCpZ9ooea/Zc/MTnj1ccc9C7rqcoinKVQLhLoi6jw6yueSf4y4tg/YIUiPPVMlEAG7ZCPS+NVmqxisQ+mOsSw== dependencies: jsonc-parser "^2.2.0" vscode-languageserver-textdocument "^1.0.0-next.4" diff --git a/extensions/markdown-language-features/schemas/package.schema.json b/extensions/markdown-language-features/schemas/package.schema.json index e76ce046c2..5591d0b003 100644 --- a/extensions/markdown-language-features/schemas/package.schema.json +++ b/extensions/markdown-language-features/schemas/package.schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", "title": "Markdown contributions to package.json", "type": "object", "properties": { @@ -29,4 +29,4 @@ } } } -} \ No newline at end of file +} diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 079bb4873c..2c2a204464 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -249,8 +249,11 @@ export class MarkdownEngine { if (uri.path[0] === '/') { const root = vscode.workspace.getWorkspaceFolder(this.currentDocument!); if (root) { - uri = uri.with({ - path: path.join(root.uri.fsPath, uri.path), + const fileUri = vscode.Uri.file(path.join(root.uri.fsPath, uri.fsPath)); + uri = fileUri.with({ + scheme: uri.scheme, + fragment: uri.fragment, + query: uri.query, }); } } diff --git a/extensions/package.json b/extensions/package.json index d1030bbe6d..65d512cb8a 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.7.3-insiders.20191123" + "typescript": "3.7.3" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/search-result/package.json b/extensions/search-result/package.json index bc7c6cf951..5e7482b603 100644 --- a/extensions/search-result/package.json +++ b/extensions/search-result/package.json @@ -19,6 +19,11 @@ "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:search-result ./tsconfig.json" }, "contributes": { + "configurationDefaults": { + "[search-result]": { + "editor.lineNumbers": "off" + } + }, "commands": [ { "command": "searchResult.rerunSearch", @@ -28,6 +33,15 @@ "light": "./src/media/refresh-light.svg", "dark": "./src/media/refresh-dark.svg" } + }, + { + "command": "searchResult.rerunSearchWithContext", + "title": "%searchResult.rerunSearchWithContext.title%", + "category": "Search Result", + "icon": { + "light": "./src/media/refresh-light.svg", + "dark": "./src/media/refresh-dark.svg" + } } ], "menus": { @@ -35,6 +49,7 @@ { "command": "searchResult.rerunSearch", "when": "editorLangId == search-result", + "alt": "searchResult.rerunSearchWithContext", "group": "navigation" } ] diff --git a/extensions/search-result/package.nls.json b/extensions/search-result/package.nls.json index 694f6b61d8..ce90d23c09 100644 --- a/extensions/search-result/package.nls.json +++ b/extensions/search-result/package.nls.json @@ -1,5 +1,6 @@ { "displayName": "Search Result", "description": "Provides syntax highlighting and language features for tabbed search results.", - "searchResult.rerunSearch.title": "Search Again" + "searchResult.rerunSearch.title": "Search Again", + "searchResult.rerunSearchWithContext.title": "Search Again (With Context)" } diff --git a/extensions/search-result/src/extension.ts b/extensions/search-result/src/extension.ts index 56cbecd087..54421536d4 100644 --- a/extensions/search-result/src/extension.ts +++ b/extensions/search-result/src/extension.ts @@ -7,81 +7,93 @@ import * as vscode from 'vscode'; import * as pathUtils from 'path'; const FILE_LINE_REGEX = /^(\S.*):$/; -const RESULT_LINE_REGEX = /^(\s+)(\d+):(\s+)(.*)$/; +const RESULT_LINE_REGEX = /^(\s+)(\d+)(:| )(\s+)(.*)$/; const SEARCH_RESULT_SELECTOR = { language: 'search-result' }; +const DIRECTIVES = ['# Query:', '# Flags:', '# Including:', '# Excluding:', '# ContextLines:']; +const FLAGS = ['RegExp', 'CaseSensitive', 'IgnoreExcludeSettings', 'WordMatch']; let cachedLastParse: { version: number, parse: ParsedSearchResults } | undefined; -export function activate() { +export function activate(context: vscode.ExtensionContext) { + context.subscriptions.push( + vscode.commands.registerCommand('searchResult.rerunSearch', () => vscode.commands.executeCommand('search.action.rerunEditorSearch')), + vscode.commands.registerCommand('searchResult.rerunSearchWithContext', () => vscode.commands.executeCommand('search.action.rerunEditorSearchWithContext')), - vscode.commands.registerCommand('searchResult.rerunSearch', () => vscode.commands.executeCommand('search.action.rerunEditorSearch')); + vscode.languages.registerDocumentSymbolProvider(SEARCH_RESULT_SELECTOR, { + provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentSymbol[] { + const results = parseSearchResults(document, token) + .filter(isFileLine) + .map(line => new vscode.DocumentSymbol( + line.path, + '', + vscode.SymbolKind.File, + line.allLocations.map(({ originSelectionRange }) => originSelectionRange!).reduce((p, c) => p.union(c), line.location.originSelectionRange!), + line.location.originSelectionRange!, + )); - vscode.languages.registerDocumentSymbolProvider(SEARCH_RESULT_SELECTOR, { - provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentSymbol[] { - const results = parseSearchResults(document, token) - .filter(isFileLine) - .map(line => new vscode.DocumentSymbol( - line.path, - '', - vscode.SymbolKind.File, - line.allLocations.map(({ originSelectionRange }) => originSelectionRange!).reduce((p, c) => p.union(c), line.location.originSelectionRange!), - line.location.originSelectionRange!, - )); - - return results; - } - }); - - vscode.languages.registerCompletionItemProvider(SEARCH_RESULT_SELECTOR, { - provideCompletionItems(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] { - - const line = document.lineAt(position.line); - if (position.line > 3) { return []; } - if (position.character === 0 || (position.character === 1 && line.text === '#')) { - const header = Array.from({ length: 4 }).map((_, i) => document.lineAt(i).text); - - return ['# Query:', '# Flags:', '# Including:', '# Excluding:'] - .filter(suggestion => header.every(line => line.indexOf(suggestion) === -1)) - .map(flag => ({ label: flag, insertText: (flag.slice(position.character)) + ' ' })); + return results; } + }), - if (line.text.indexOf('# Flags:') === -1) { return []; } + vscode.languages.registerCompletionItemProvider(SEARCH_RESULT_SELECTOR, { + provideCompletionItems(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] { - return ['RegExp', 'CaseSensitive', 'IgnoreExcludeSettings', 'WordMatch'] - .filter(flag => line.text.indexOf(flag) === -1) - .map(flag => ({ label: flag, insertText: flag + ' ' })); - } - }, '#'); + const line = document.lineAt(position.line); + if (position.line > 3) { return []; } + if (position.character === 0 || (position.character === 1 && line.text === '#')) { + const header = Array.from({ length: DIRECTIVES.length }).map((_, i) => document.lineAt(i).text); - vscode.languages.registerDefinitionProvider(SEARCH_RESULT_SELECTOR, { - provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.DefinitionLink[] { - const lineResult = parseSearchResults(document, token)[position.line]; - if (!lineResult) { return []; } - if (lineResult.type === 'file') { - // TODO: The multi-match peek UX isnt very smooth. - // return lineResult.allLocations.length > 1 ? lineResult.allLocations : [lineResult.location]; - return []; + return DIRECTIVES + .filter(suggestion => header.every(line => line.indexOf(suggestion) === -1)) + .map(flag => ({ label: flag, insertText: (flag.slice(position.character)) + ' ' })); + } + + if (line.text.indexOf('# Flags:') === -1) { return []; } + + return FLAGS + .filter(flag => line.text.indexOf(flag) === -1) + .map(flag => ({ label: flag, insertText: flag + ' ' })); } + }, '#'), - return [lineResult.location]; - } - }); + vscode.languages.registerDefinitionProvider(SEARCH_RESULT_SELECTOR, { + provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): vscode.DefinitionLink[] { + const lineResult = parseSearchResults(document, token)[position.line]; + if (!lineResult) { return []; } + if (lineResult.type === 'file') { + // TODO: The multi-match peek UX isnt very smooth. + // return lineResult.allLocations.length > 1 ? lineResult.allLocations : [lineResult.location]; + return []; + } - vscode.languages.registerDocumentLinkProvider(SEARCH_RESULT_SELECTOR, { - async provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { - return parseSearchResults(document, token) - .filter(({ type }) => type === 'file') - .map(({ location }) => ({ range: location.originSelectionRange!, target: location.targetUri })); - } - }); + const translateRangeSidewaysBy = (r: vscode.Range, n: number) => + r.with({ start: new vscode.Position(r.start.line, Math.max(0, n - r.start.character)), end: new vscode.Position(r.end.line, Math.max(0, n - r.end.character)) }); - vscode.window.onDidChangeActiveTextEditor(e => { - if (e?.document.languageId === 'search-result') { - // Clear the parse whenever we open a new editor. - // Conservative because things like the URI might remain constant even if the contents change, and re-parsing even large files is relatively fast. - cachedLastParse = undefined; - } - }); + return [{ + ...lineResult.location, + targetSelectionRange: translateRangeSidewaysBy(lineResult.location.targetSelectionRange!, position.character - 1) + }]; + } + }), + + vscode.languages.registerDocumentLinkProvider(SEARCH_RESULT_SELECTOR, { + async provideDocumentLinks(document: vscode.TextDocument, token: vscode.CancellationToken): Promise { + return parseSearchResults(document, token) + .filter(({ type }) => type === 'file') + .map(({ location }) => ({ range: location.originSelectionRange!, target: location.targetUri })); + } + }), + + vscode.window.onDidChangeActiveTextEditor(e => { + if (e?.document.languageId === 'search-result') { + // Clear the parse whenever we open a new editor. + // Conservative because things like the URI might remain constant even if the contents change, and re-parsing even large files is relatively fast. + cachedLastParse = undefined; + } + }), + + { dispose() { cachedLastParse = undefined; } } + ); } @@ -160,13 +172,14 @@ function parseSearchResults(document: vscode.TextDocument, token: vscode.Cancell const resultLine = RESULT_LINE_REGEX.exec(line); if (resultLine) { - const [, indentation, _lineNumber, resultIndentation] = resultLine; + const [, indentation, _lineNumber, seperator, resultIndentation] = resultLine; const lineNumber = +_lineNumber - 1; - const resultStart = (indentation + _lineNumber + ':' + resultIndentation).length; + const resultStart = (indentation + _lineNumber + seperator + resultIndentation).length; + const metadataOffset = (indentation + _lineNumber + seperator).length; const location: vscode.LocationLink = { targetRange: new vscode.Range(Math.max(lineNumber - 3, 0), 0, lineNumber + 3, line.length), - targetSelectionRange: new vscode.Range(lineNumber, 0, lineNumber, line.length), + targetSelectionRange: new vscode.Range(lineNumber, metadataOffset, lineNumber, metadataOffset), targetUri: currentTarget, originSelectionRange: new vscode.Range(i, resultStart, i, line.length), }; diff --git a/extensions/search-result/syntaxes/searchResult.tmLanguage.json b/extensions/search-result/syntaxes/searchResult.tmLanguage.json index 4de2a40ba4..d16ecb8c97 100644 --- a/extensions/search-result/syntaxes/searchResult.tmLanguage.json +++ b/extensions/search-result/syntaxes/searchResult.tmLanguage.json @@ -3,7 +3,7 @@ "scopeName": "text.searchResult", "patterns": [ { - "match": "^# (Query|Flags|Including|Excluding): .*$", + "match": "^# (Query|Flags|Including|Excluding|ContextLines): .*$", "name": "comment" }, { diff --git a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts index ebd24ee350..79e15544a7 100644 --- a/extensions/vscode-colorize-tests/src/colorizerTestMain.ts +++ b/extensions/vscode-colorize-tests/src/colorizerTestMain.ts @@ -8,31 +8,43 @@ import * as jsoncParser from 'jsonc-parser'; export function activate(context: vscode.ExtensionContext): any { - const tokenModifiers = ['static', 'abstract', 'deprecated']; - const tokenTypes = ['strings', 'types', 'structs', 'classes', 'functions', 'variables']; + const tokenTypes = ['type', 'struct', 'class', 'interface', 'enum', 'parameterType', 'function', 'variable']; + const tokenModifiers = ['static', 'abstract', 'deprecated', 'declaration', 'documentation', 'member', 'async']; + const legend = new vscode.SemanticTokensLegend(tokenTypes, tokenModifiers); const semanticHighlightProvider: vscode.SemanticTokensProvider = { provideSemanticTokens(document: vscode.TextDocument): vscode.ProviderResult { const builder = new vscode.SemanticTokensBuilder(); + function addToken(value: string, startLine: number, startCharacter: number, length: number) { + const [type, ...modifiers] = value.split('.'); + + let tokenType = legend.tokenTypes.indexOf(type); + if (tokenType === -1) { + return; + } + + let tokenModifiers = 0; + for (let i = 0; i < modifiers.length; i++) { + const index = legend.tokenModifiers.indexOf(modifiers[i]); + if (index !== -1) { + tokenModifiers = tokenModifiers | 1 << index; + } + } + + + builder.push(startLine, startCharacter, length, tokenType, tokenModifiers); + } + const visitor: jsoncParser.JSONVisitor = { - onObjectProperty: (property: string, _offset: number, length: number, startLine: number, startCharacter: number) => { - const [type, ...modifiers] = property.split('.'); - let tokenType = legend.tokenTypes.indexOf(type); - if (tokenType === -1) { - tokenType = 0; + onObjectProperty: (property: string, _offset: number, _length: number, startLine: number, startCharacter: number) => { + addToken(property, startLine, startCharacter, property.length + 2); + }, + onLiteralValue: (value: any, _offset: number, length: number, startLine: number, startCharacter: number) => { + if (typeof value === 'string') { + addToken(value, startLine, startCharacter, length); } - - let tokenModifiers = 0; - for (let i = 0; i < modifiers.length; i++) { - const index = legend.tokenModifiers.indexOf(modifiers[i]); - if (index !== -1) { - tokenModifiers = tokenModifiers | 1 << index; - } - } - - builder.push(startLine, startCharacter, length, tokenType, tokenModifiers); } }; jsoncParser.visit(document.getText(), visitor); @@ -42,6 +54,6 @@ export function activate(context: vscode.ExtensionContext): any { }; - context.subscriptions.push(vscode.languages.registerSemanticTokensProvider({ pattern: '**/color-test.json' }, semanticHighlightProvider, legend)); + context.subscriptions.push(vscode.languages.registerSemanticTokensProvider({ pattern: '**/*semantic-test.json' }, semanticHighlightProvider, legend)); } diff --git a/extensions/yarn.lock b/extensions/yarn.lock index f2e7ae9507..a8eb51df65 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.7.3-insiders.20191123: - version "3.7.3-insiders.20191123" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3-insiders.20191123.tgz#f3bef33a2a3f6e02f11bcc0c20b6f0de526f17fd" - integrity sha512-b+tLx4D0a6SeuaCa7iehdgkRKHsS67FkioQWw+0REjVNOYZ+AqJ0NjlnomK1hEUvSzSNrH9Du+m+Yiv7JlVpSg== +typescript@3.7.3: + version "3.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.3.tgz#b36840668a16458a7025b9eabfad11b66ab85c69" + integrity sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw== diff --git a/package.json b/package.json index 5d5745a5f8..deebcaf508 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "azuredatastudio", "version": "1.14.0", - "distro": "68a9088c1675337aaa88e0e9853ea68c32a0409f", + "distro": "c2d125e08e84d4212f7b634dd8c0deaaca2bf4bd", "author": { "name": "Microsoft Corporation" }, @@ -77,7 +77,7 @@ "vscode-ripgrep": "^1.5.7", "vscode-sqlite3": "4.0.9", "vscode-textmate": "4.4.0", - "xterm": "4.3.0-beta.28", + "xterm": "4.3.0-beta.28.vscode.1", "xterm-addon-search": "0.4.0-beta4", "xterm-addon-web-links": "0.2.1", "xterm-addon-webgl": "0.4.0-beta.11", diff --git a/remote/package.json b/remote/package.json index 216a7c3701..891ae131f6 100644 --- a/remote/package.json +++ b/remote/package.json @@ -20,7 +20,7 @@ "vscode-proxy-agent": "^0.5.2", "vscode-ripgrep": "^1.5.7", "vscode-textmate": "4.4.0", - "xterm": "4.3.0-beta.28", + "xterm": "4.3.0-beta.28.vscode.1", "xterm-addon-search": "0.4.0-beta4", "xterm-addon-web-links": "0.2.1", "xterm-addon-webgl": "0.4.0-beta.11", diff --git a/remote/web/package.json b/remote/web/package.json index 2a0519d282..0aed404aab 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -5,7 +5,7 @@ "onigasm-umd": "2.2.5", "semver-umd": "^5.5.3", "vscode-textmate": "4.4.0", - "xterm": "4.3.0-beta.28", + "xterm": "4.3.0-beta.28.vscode.1", "xterm-addon-search": "0.4.0-beta4", "xterm-addon-web-links": "0.2.1", "xterm-addon-webgl": "0.4.0-beta.11" diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 1e2d3f0de2..18078da8d1 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -46,7 +46,7 @@ xterm-addon-webgl@0.4.0-beta.11: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta.11.tgz#0e4a7242e2353cf74aba55e5a5bdc0b4ec87ad10" integrity sha512-AteDxm1RFy1WnjY9r5iJSETozLebvUkR+jextdZk/ASsK21vsYK0DuVWwRI8afgiN2hUVhxcxuHEJUOV+CJDQA== -xterm@4.3.0-beta.28: - version "4.3.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.tgz#80f7c4ba8f6ee3c953e6f33f8ce5aef08d5a8354" - integrity sha512-WWZ4XCvce5h+klL6ObwtMauJff/n2KGGOwJJkDbJhrAjVy2a77GKgAedJTDDFGgKJ6ix1d7puHtVSSKflIVaDQ== +xterm@4.3.0-beta.28.vscode.1: + version "4.3.0-beta.28.vscode.1" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.vscode.1.tgz#89b85398b5801708e833d08bf92b2124fa128943" + integrity sha512-JNHNZyDtAWnybJTrenPzD6g/yXpHOvPqmjau91Up4onRbjQYMSNlth17SqaES68DKn/+4kcIl2c/RG5SXJjvGw== diff --git a/remote/yarn.lock b/remote/yarn.lock index 6b661b17e4..a45c4d94ff 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -433,10 +433,10 @@ xterm-addon-webgl@0.4.0-beta.11: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta.11.tgz#0e4a7242e2353cf74aba55e5a5bdc0b4ec87ad10" integrity sha512-AteDxm1RFy1WnjY9r5iJSETozLebvUkR+jextdZk/ASsK21vsYK0DuVWwRI8afgiN2hUVhxcxuHEJUOV+CJDQA== -xterm@4.3.0-beta.28: - version "4.3.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.tgz#80f7c4ba8f6ee3c953e6f33f8ce5aef08d5a8354" - integrity sha512-WWZ4XCvce5h+klL6ObwtMauJff/n2KGGOwJJkDbJhrAjVy2a77GKgAedJTDDFGgKJ6ix1d7puHtVSSKflIVaDQ== +xterm@4.3.0-beta.28.vscode.1: + version "4.3.0-beta.28.vscode.1" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.vscode.1.tgz#89b85398b5801708e833d08bf92b2124fa128943" + integrity sha512-JNHNZyDtAWnybJTrenPzD6g/yXpHOvPqmjau91Up4onRbjQYMSNlth17SqaES68DKn/+4kcIl2c/RG5SXJjvGw== yauzl@^2.9.2: version "2.10.0" diff --git a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts index d1924f4c76..e06c6da4af 100644 --- a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts +++ b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts @@ -77,7 +77,7 @@ export class QueryTextEditor extends BaseTextEditor { if (!this._selected) { options.renderLineHighlight = 'none'; options.parameterHints = { enabled: false }; - options.matchBrackets = false; + options.matchBrackets = 'never'; } if (this._hideLineNumbers) { options.lineNumbers = 'off'; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 7a886f900a..b6006139eb 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -972,6 +972,7 @@ export const EventHelper = { export interface IFocusTracker extends Disposable { onDidFocus: Event; onDidBlur: Event; + refreshState?(): void; } export function saveParentsScrollTop(node: Element): number[] { @@ -1000,6 +1001,8 @@ class FocusTracker extends Disposable implements IFocusTracker { private readonly _onDidBlur = this._register(new Emitter()); public readonly onDidBlur: Event = this._onDidBlur.event; + private _refreshStateHandler: () => void; + constructor(element: HTMLElement | Window) { super(); let hasFocus = isAncestor(document.activeElement, element); @@ -1026,9 +1029,24 @@ class FocusTracker extends Disposable implements IFocusTracker { } }; + this._refreshStateHandler = () => { + let currentNodeHasFocus = isAncestor(document.activeElement, element); + if (currentNodeHasFocus !== hasFocus) { + if (hasFocus) { + onBlur(); + } else { + onFocus(); + } + } + }; + this._register(domEvent(element, EventType.FOCUS, true)(onFocus)); this._register(domEvent(element, EventType.BLUR, true)(onBlur)); } + + refreshState() { + this._refreshStateHandler(); + } } export function trackFocus(element: HTMLElement | Window): IFocusTracker { diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index b75bdaae7a..95ead7a29b 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -15,7 +15,6 @@ import { cloneAndChange } from 'vs/base/common/objects'; import { escape } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; export interface MarkdownRenderOptions extends FormattedTextRenderOptions { codeBlockRenderer?: (modeId: string, value: string) => Promise; @@ -73,10 +72,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende const renderer = new marked.Renderer(); renderer.image = (href: string, title: string, text: string) => { - if (href && href.indexOf('vscode-icon://codicon/') === 0) { - return renderCodicons(`$(${URI.parse(href).path.substr(1)})`); - } - let dimensions: string[] = []; let attributes: string[] = []; if (href) { diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css index 0e31a773e9..a72a2128ef 100644 --- a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -396,7 +396,6 @@ .codicon-debug-breakpoint-function:before { content: "\eb88" } .codicon-debug-breakpoint-function-disabled:before { content: "\eb88" } .codicon-debug-breakpoint-stackframe-active:before { content: "\eb89" } -.codicon-debug-breakpoint-stackframe-dot:before { content: "\eb8a" } .codicon-debug-breakpoint-stackframe:before { content: "\eb8b" } .codicon-debug-breakpoint-stackframe-focused:before { content: "\eb8b" } .codicon-debug-breakpoint-unsupported:before { content: "\eb8c" } diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index 4e09823bb0..3dd57bf12a 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -604,8 +604,8 @@ export class SerializableGrid extends Grid { export type GridNodeDescriptor = { size?: number, groups?: GridNodeDescriptor[] }; export type GridDescriptor = { orientation: Orientation, groups?: GridNodeDescriptor[] }; -export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor): void { - if (nodeDescriptor.groups && nodeDescriptor.groups.length <= 1) { +export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor, rootNode: boolean): void { + if (!rootNode && nodeDescriptor.groups && nodeDescriptor.groups.length <= 1) { nodeDescriptor.groups = undefined; } @@ -617,7 +617,7 @@ export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor): let totalDefinedSizeCount = 0; for (const child of nodeDescriptor.groups) { - sanitizeGridNodeDescriptor(child); + sanitizeGridNodeDescriptor(child, false); if (child.size) { totalDefinedSize += child.size; @@ -665,7 +665,7 @@ function getDimensions(node: ISerializedNode, orientation: Orientation): { width } export function createSerializedGrid(gridDescriptor: GridDescriptor): ISerializedGrid { - sanitizeGridNodeDescriptor(gridDescriptor); + sanitizeGridNodeDescriptor(gridDescriptor, true); const root = createSerializedNode(gridDescriptor); const { width, height } = getDimensions(root, gridDescriptor.orientation); diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 9f46f9ba78..b040214086 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -1086,6 +1086,8 @@ export class GridView implements IDisposable { throw new Error('Invalid JSON: \'width\' property must be a number.'); } else if (typeof json.height !== 'number') { throw new Error('Invalid JSON: \'height\' property must be a number.'); + } else if (json.root?.type !== 'branch') { + throw new Error('Invalid JSON: \'root\' property must have \'type\' value of branch.'); } const orientation = json.orientation; diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index 04bcb1f416..a1eaa1ecd7 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -9,6 +9,7 @@ import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlighte import { IMatch } from 'vs/base/common/filters'; import { Disposable } from 'vs/base/common/lifecycle'; import { Range } from 'vs/base/common/range'; +import { equals } from 'vs/base/common/objects'; export interface IIconLabelCreationOptions { supportHighlights?: boolean; @@ -165,15 +166,17 @@ class Label { private label: string | string[] | undefined = undefined; private singleLabel: HTMLElement | undefined = undefined; + private options: IIconLabelValueOptions | undefined; constructor(private container: HTMLElement) { } setLabel(label: string | string[], options?: IIconLabelValueOptions): void { - if (this.label === label) { + if (this.label === label && equals(this.options, options)) { return; } this.label = label; + this.options = options; if (typeof label === 'string') { if (!this.singleLabel) { @@ -226,15 +229,17 @@ class LabelWithHighlights { private label: string | string[] | undefined = undefined; private singleLabel: HighlightedLabel | undefined = undefined; + private options: IIconLabelValueOptions | undefined; constructor(private container: HTMLElement, private supportCodicons: boolean) { } setLabel(label: string | string[], options?: IIconLabelValueOptions): void { - if (this.label === label) { + if (this.label === label && equals(this.options, options)) { return; } this.label = label; + this.options = options; if (typeof label === 'string') { if (!this.singleLabel) { diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 58a24e40dd..4388d38b31 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -1189,14 +1189,16 @@ export class CompressibleAsyncDataTree extends As if (compressedNode) { for (let i = 0; i < compressedNode.elements.length; i++) { const id = getId(compressedNode.elements[i].element as T); + const element = compressedNode.elements[compressedNode.elements.length - 1].element as T; - if (oldSelection.has(id)) { - selection.push(compressedNode.elements[compressedNode.elements.length - 1].element as T); + // github.com/microsoft/vscode/issues/85938 + if (oldSelection.has(id) && selection.indexOf(element) === -1) { + selection.push(element); didChangeSelection = true; } - if (oldFocus.has(id)) { - focus.push(compressedNode.elements[compressedNode.elements.length - 1].element as T); + if (oldFocus.has(id) && focus.indexOf(element) === -1) { + focus.push(element); didChangeFocus = true; } } diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts index 7aa3d01a1f..7b8bc76b5c 100644 --- a/src/vs/base/node/encoding.ts +++ b/src/vs/base/node/encoding.ts @@ -199,6 +199,13 @@ export function detectEncodingByBOMFromBuffer(buffer: Buffer | VSBuffer | null, return null; } +// we explicitly ignore a specific set of encodings from auto guessing +// - ASCII: we never want this encoding (most UTF-8 files would happily detect as +// ASCII files and then you could not type non-ASCII characters anymore) +// - UTF-16: we have our own detection logic for UTF-16 +// - UTF-32: we do not support this encoding in VSCode +const IGNORE_ENCODINGS = ['ascii', 'utf-16', 'utf-32']; + /** * Guesses the encoding from buffer. */ @@ -210,15 +217,9 @@ async function guessEncodingByBuffer(buffer: Buffer): Promise { return null; } - // Ignore 'ascii' as guessed encoding because that - // is almost never what we want, rather fallback - // to the configured encoding then. Otherwise, - // opening a ascii-only file with auto guessing - // enabled will put the file into 'ascii' mode - // and thus typing any special characters is - // not possible anymore. - if (guessed.encoding.toLowerCase() === 'ascii') { - return null; + const enc = guessed.encoding.toLowerCase(); + if (0 <= IGNORE_ENCODINGS.indexOf(enc)) { + return null; // see comment above why we ignore some encodings } return toIconvLiteEncoding(guessed.encoding); diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 6781bbf8e4..1373d395ea 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -9,11 +9,10 @@ import { StandardWheelEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent import { RunOnceScheduler, TimeoutTimer } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; -import { HitTestContext, IViewZoneData, MouseTarget, MouseTargetFactory } from 'vs/editor/browser/controller/mouseTarget'; +import { HitTestContext, IViewZoneData, MouseTarget, MouseTargetFactory, PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { ClientCoordinates, EditorMouseEvent, EditorMouseEventFactory, GlobalEditorMouseMoveMonitor, createEditorPagePosition } from 'vs/editor/browser/editorDom'; import { ViewController } from 'vs/editor/browser/view/viewController'; -import { IViewCursorRenderData } from 'vs/editor/browser/viewParts/viewCursors/viewCursor'; import { EditorZoom } from 'vs/editor/common/config/editorZoom'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; @@ -46,9 +45,9 @@ export interface IPointerHandlerHelper { focusTextArea(): void; /** - * Get the last rendered information of the cursors. + * Get the last rendered information for cursors & textarea. */ - getLastViewCursorsRenderData(): IViewCursorRenderData[]; + getLastRenderData(): PointerHandlerLastRenderData; shouldSuppressMouseDownOnViewZone(viewZoneId: string): boolean; shouldSuppressMouseDownOnWidget(widgetId: string): boolean; @@ -158,13 +157,11 @@ export class MouseHandler extends ViewEventHandler { return null; } - const lastViewCursorsRenderData = this.viewHelper.getLastViewCursorsRenderData(); - return this.mouseTargetFactory.createMouseTarget(lastViewCursorsRenderData, editorPos, pos, null); + return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), editorPos, pos, null); } protected _createMouseTarget(e: EditorMouseEvent, testEventTarget: boolean): editorBrowser.IMouseTarget { - const lastViewCursorsRenderData = this.viewHelper.getLastViewCursorsRenderData(); - return this.mouseTargetFactory.createMouseTarget(lastViewCursorsRenderData, e.editorPos, e.pos, testEventTarget ? e.target : null); + return this.mouseTargetFactory.createMouseTarget(this.viewHelper.getLastRenderData(), e.editorPos, e.pos, testEventTarget ? e.target : null); } private _getMouseColumn(e: EditorMouseEvent): number { diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 51d3080b2b..c1e8137ed0 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -89,6 +89,13 @@ interface IHitTestResult { hitTarget: Element | null; } +export class PointerHandlerLastRenderData { + constructor( + public readonly lastViewCursorsRenderData: IViewCursorRenderData[], + public readonly lastTextareaPosition: Position | null + ) { } +} + export class MouseTarget implements IMouseTarget { public readonly element: Element | null; @@ -232,19 +239,19 @@ export class HitTestContext { public readonly viewDomNode: HTMLElement; public readonly lineHeight: number; public readonly typicalHalfwidthCharacterWidth: number; - public readonly lastViewCursorsRenderData: IViewCursorRenderData[]; + public readonly lastRenderData: PointerHandlerLastRenderData; private readonly _context: ViewContext; private readonly _viewHelper: IPointerHandlerHelper; - constructor(context: ViewContext, viewHelper: IPointerHandlerHelper, lastViewCursorsRenderData: IViewCursorRenderData[]) { + constructor(context: ViewContext, viewHelper: IPointerHandlerHelper, lastRenderData: PointerHandlerLastRenderData) { this.model = context.model; const options = context.configuration.options; this.layoutInfo = options.get(EditorOption.layoutInfo); this.viewDomNode = viewHelper.viewDomNode; this.lineHeight = options.get(EditorOption.lineHeight); this.typicalHalfwidthCharacterWidth = options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth; - this.lastViewCursorsRenderData = lastViewCursorsRenderData; + this.lastRenderData = lastRenderData; this._context = context; this._viewHelper = viewHelper; } @@ -462,8 +469,8 @@ export class MouseTargetFactory { return false; } - public createMouseTarget(lastViewCursorsRenderData: IViewCursorRenderData[], editorPos: EditorPagePosition, pos: PageCoordinates, target: HTMLElement | null): IMouseTarget { - const ctx = new HitTestContext(this._context, this._viewHelper, lastViewCursorsRenderData); + public createMouseTarget(lastRenderData: PointerHandlerLastRenderData, editorPos: EditorPagePosition, pos: PageCoordinates, target: HTMLElement | null): IMouseTarget { + const ctx = new HitTestContext(this._context, this._viewHelper, lastRenderData); const request = new HitTestRequest(ctx, editorPos, pos, target); try { const r = MouseTargetFactory._createMouseTarget(ctx, request, false); @@ -544,7 +551,7 @@ export class MouseTargetFactory { if (request.target) { // Check if we've hit a painted cursor - const lastViewCursorsRenderData = ctx.lastViewCursorsRenderData; + const lastViewCursorsRenderData = ctx.lastRenderData.lastViewCursorsRenderData; for (const d of lastViewCursorsRenderData) { @@ -560,7 +567,7 @@ export class MouseTargetFactory { // first or last rendered view line dom node, therefore help it out // and first check if we are on top of a cursor - const lastViewCursorsRenderData = ctx.lastViewCursorsRenderData; + const lastViewCursorsRenderData = ctx.lastRenderData.lastViewCursorsRenderData; const mouseContentHorizontalOffset = request.mouseContentHorizontalOffset; const mouseVerticalOffset = request.mouseVerticalOffset; @@ -602,7 +609,10 @@ export class MouseTargetFactory { private static _hitTestTextArea(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { // Is it the textarea? if (ElementPath.isTextArea(request.targetPath)) { - return request.fulfill(MouseTargetType.TEXTAREA); + if (ctx.lastRenderData.lastTextareaPosition) { + return request.fulfill(MouseTargetType.CONTENT_TEXT, ctx.lastRenderData.lastTextareaPosition); + } + return request.fulfill(MouseTargetType.TEXTAREA, ctx.lastRenderData.lastTextareaPosition); } return null; } diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index c5f36bae59..6594d31749 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -77,6 +77,12 @@ export class TextAreaHandler extends ViewPart { private _visibleTextArea: VisibleTextAreaData | null; private _selections: Selection[]; + /** + * The position at which the textarea was rendered. + * This is useful for hit-testing and determining the mouse position. + */ + private _lastRenderPosition: Position | null; + public readonly textArea: FastDomNode; public readonly textAreaCover: FastDomNode; private readonly _textAreaInput: TextAreaInput; @@ -104,6 +110,7 @@ export class TextAreaHandler extends ViewPart { this._visibleTextArea = null; this._selections = [new Selection(1, 1, 1, 1)]; + this._lastRenderPosition = null; // Text Area (The focus will always be in the textarea when the cursor is blinking) this.textArea = createFastDomNode(document.createElement('textarea')); @@ -413,13 +420,18 @@ export class TextAreaHandler extends ViewPart { this._textAreaInput.refreshFocusState(); } + public getLastRenderData(): Position | null { + return this._lastRenderPosition; + } + // --- end view API + private _primaryCursorPosition: Position = new Position(1, 1); private _primaryCursorVisibleRange: HorizontalPosition | null = null; public prepareRender(ctx: RenderingContext): void { - const primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn); - this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(primaryCursorPosition); + this._primaryCursorPosition = new Position(this._selections[0].positionLineNumber, this._selections[0].positionColumn); + this._primaryCursorVisibleRange = ctx.visibleRangeForPosition(this._primaryCursorPosition); } public render(ctx: RestrictedRenderingContext): void { @@ -431,6 +443,7 @@ export class TextAreaHandler extends ViewPart { if (this._visibleTextArea) { // The text area is visible for composition reasons this._renderInsideEditor( + null, this._visibleTextArea.top - this._scrollTop, this._contentLeft + this._visibleTextArea.left - this._scrollLeft, this._visibleTextArea.width, @@ -465,6 +478,7 @@ export class TextAreaHandler extends ViewPart { // For the popup emoji input, we will make the text area as high as the line height // We will also make the fontSize and lineHeight the correct dimensions to help with the placement of these pickers this._renderInsideEditor( + this._primaryCursorPosition, top, left, canUseZeroSizeTextarea ? 0 : 1, this._lineHeight ); @@ -472,12 +486,14 @@ export class TextAreaHandler extends ViewPart { } this._renderInsideEditor( + this._primaryCursorPosition, top, left, canUseZeroSizeTextarea ? 0 : 1, canUseZeroSizeTextarea ? 0 : 1 ); } - private _renderInsideEditor(top: number, left: number, width: number, height: number): void { + private _renderInsideEditor(renderedPosition: Position | null, top: number, left: number, width: number, height: number): void { + this._lastRenderPosition = renderedPosition; const ta = this.textArea; const tac = this.textAreaCover; @@ -495,6 +511,7 @@ export class TextAreaHandler extends ViewPart { } private _renderAtTopLeft(): void { + this._lastRenderPosition = null; const ta = this.textArea; const tac = this.textAreaCover; diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index e5f64de83c..782d382640 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -48,6 +48,7 @@ import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; import { IThemeService, getThemeTypeSelector } from 'vs/platform/theme/common/themeService'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; export interface IContentWidgetData { @@ -244,8 +245,10 @@ export class View extends ViewEventHandler { this.focus(); }, - getLastViewCursorsRenderData: () => { - return this.viewCursors.getLastRenderData() || []; + getLastRenderData: (): PointerHandlerLastRenderData => { + const lastViewCursorsRenderData = this.viewCursors.getLastRenderData() || []; + const lastTextareaPosition = this._textAreaHandler.getLastRenderData(); + return new PointerHandlerLastRenderData(lastViewCursorsRenderData, lastTextareaPosition); }, shouldSuppressMouseDownOnViewZone: (viewZoneId: string) => { return this.viewZones.shouldSuppressMouseDownOnViewZone(viewZoneId); diff --git a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts index 7d613b77ae..53afdd7410 100644 --- a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts +++ b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts @@ -57,7 +57,7 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay { const renderSelections = isRenderedUsingBorder ? this._selections.slice(0, 1) : this._selections; const cursorsLineNumbers = renderSelections.map(s => s.positionLineNumber); - cursorsLineNumbers.sort(); + cursorsLineNumbers.sort((a, b) => a - b); if (!arrays.equals(this._cursorLineNumbers, cursorsLineNumbers)) { this._cursorLineNumbers = cursorsLineNumbers; hasChanged = true; diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 101667d0e6..f04e5b657b 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -874,6 +874,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE public onHide(): void { this._modelData?.view.refreshFocusState(); + this._focusTracker.refreshState(); } public getContribution(id: string): T { @@ -1806,6 +1807,12 @@ class CodeEditorWidgetFocusTracker extends Disposable { public hasFocus(): boolean { return this._hasFocus; } + + public refreshState(): void { + if (this._domFocusTracker.refreshState) { + this._domFocusTracker.refreshState(); + } + } } const squigglyStart = encodeURIComponent(`matchBrackets === true) { + options.matchBrackets = 'always'; + } else if (matchBrackets === false) { + options.matchBrackets = 'never'; + } } function deepCloneAndMigrateOptions(_options: IEditorOptions): IEditorOptions { diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 4958674ce9..5b7a878a5c 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -481,9 +481,9 @@ export interface IEditorOptions { showFoldingControls?: 'always' | 'mouseover'; /** * Enable highlighting of matching brackets. - * Defaults to true. + * Defaults to 'always'. */ - matchBrackets?: boolean; + matchBrackets?: 'never' | 'near' | 'always'; /** * Enable rendering of whitespace. * Defaults to none. @@ -2277,7 +2277,7 @@ class EditorRulers extends SimpleEditorOption { for (let value of input) { rulers.push(EditorIntOption.clampedInt(value, 0, 0, 10000)); } - rulers.sort(); + rulers.sort((a, b) => a - b); return rulers; } return this.defaultValue; @@ -3364,9 +3364,11 @@ export const EditorOptions = { EditorOption.links, 'links', true, { description: nls.localize('links', "Controls whether the editor should detect links and make them clickable.") } )), - matchBrackets: register(new EditorBooleanOption( - EditorOption.matchBrackets, 'matchBrackets', true, - { description: nls.localize('matchBrackets', "Highlight matching brackets when one of them is selected.") } + matchBrackets: register(new EditorStringEnumOption( + EditorOption.matchBrackets, 'matchBrackets', + 'always' as 'never' | 'near' | 'always', + ['always', 'near', 'never'] as const, + { description: nls.localize('matchBrackets', "Highlight matching brackets.") } )), minimap: register(new EditorMinimap()), mouseStyle: register(new EditorStringEnumOption( diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 21e4f4c8e2..a2694d12ae 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -898,7 +898,7 @@ export interface ITextModel { * @param position The position at which to start the search. * @internal */ - findEnclosingBrackets(position: IPosition): [Range, Range] | null; + findEnclosingBrackets(position: IPosition, maxDuration?: number): [Range, Range] | null; /** * Given a `position`, if the position is on top or near a bracket, diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 3e6b13803f..b4b7eff15a 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -34,6 +34,7 @@ import { withUndefinedAsNull } from 'vs/base/common/types'; import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer'; import { TokensStore, MultilineTokens, countEOL, MultilineTokens2, TokensStore2 } from 'vs/editor/common/model/tokensStore'; import { Color } from 'vs/base/common/color'; +import { Constants } from 'vs/base/common/uint'; function createTextBufferBuilder() { return new PieceTreeTextBufferBuilder(); @@ -2341,16 +2342,21 @@ export class TextModel extends Disposable implements model.ITextModel { return null; } - public findEnclosingBrackets(_position: IPosition): [Range, Range] | null { + public findEnclosingBrackets(_position: IPosition, maxDuration = Constants.MAX_SAFE_SMALL_INTEGER): [Range, Range] | null { const position = this.validatePosition(_position); const lineCount = this.getLineCount(); + const savedCounts = new Map(); let counts: number[] = []; - const resetCounts = (modeBrackets: RichEditBrackets | null) => { - counts = []; - for (let i = 0, len = modeBrackets ? modeBrackets.brackets.length : 0; i < len; i++) { - counts[i] = 0; + const resetCounts = (languageId: number, modeBrackets: RichEditBrackets | null) => { + if (!savedCounts.has(languageId)) { + let tmp = []; + for (let i = 0, len = modeBrackets ? modeBrackets.brackets.length : 0; i < len; i++) { + tmp[i] = 0; + } + savedCounts.set(languageId, tmp); } + counts = savedCounts.get(languageId)!; }; const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null => { while (true) { @@ -2380,7 +2386,12 @@ export class TextModel extends Disposable implements model.ITextModel { let languageId: LanguageId = -1; let modeBrackets: RichEditBrackets | null = null; + const startTime = Date.now(); for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) { + const elapsedTime = Date.now() - startTime; + if (elapsedTime > maxDuration) { + return null; + } const lineTokens = this._getLineTokens(lineNumber); const tokenCount = lineTokens.getCount(); const lineText = this._buffer.getLineContent(lineNumber); @@ -2396,7 +2407,7 @@ export class TextModel extends Disposable implements model.ITextModel { if (languageId !== tokenLanguageId) { languageId = tokenLanguageId; modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId); - resetCounts(modeBrackets); + resetCounts(languageId, modeBrackets); } } @@ -2415,7 +2426,7 @@ export class TextModel extends Disposable implements model.ITextModel { } languageId = tokenLanguageId; modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId); - resetCounts(modeBrackets); + resetCounts(languageId, modeBrackets); } const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))); diff --git a/src/vs/editor/common/model/tokensStore.ts b/src/vs/editor/common/model/tokensStore.ts index 5e9cb8f86f..cfb66060aa 100644 --- a/src/vs/editor/common/model/tokensStore.ts +++ b/src/vs/editor/common/model/tokensStore.ts @@ -121,7 +121,7 @@ export interface IEncodedTokens { getMetadata(tokenIndex: number): number; clear(): void; - acceptDeleteRange(startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void; + acceptDeleteRange(horizontalShiftForFirstLineTokens: number, startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void; acceptInsertText(deltaLine: number, character: number, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void; } @@ -173,7 +173,7 @@ export class SparseEncodedTokens implements IEncodedTokens { this._tokenCount = 0; } - public acceptDeleteRange(startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void { + public acceptDeleteRange(horizontalShiftForFirstLineTokens: number, startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void { // This is a bit complex, here are the cases I used to think about this: // // 1. The token starts before the deletion range @@ -292,9 +292,13 @@ export class SparseEncodedTokens implements IEncodedTokens { tokenDeltaLine -= deletedLineCount; } else if (tokenDeltaLine === endDeltaLine && tokenStartCharacter >= endCharacter) { // 4. (continued) The token starts after the deletion range, on the last line where a deletion occurs + if (horizontalShiftForFirstLineTokens && tokenDeltaLine === 0) { + tokenStartCharacter += horizontalShiftForFirstLineTokens; + tokenEndCharacter += horizontalShiftForFirstLineTokens; + } tokenDeltaLine -= deletedLineCount; - tokenStartCharacter -= endCharacter; - tokenEndCharacter -= endCharacter; + tokenStartCharacter -= (endCharacter - startCharacter); + tokenEndCharacter -= (endCharacter - startCharacter); } else { throw new Error(`Not possible!`); } @@ -373,8 +377,8 @@ export class SparseEncodedTokens implements IEncodedTokens { } } // => the token must move and keep its size constant - tokenDeltaLine += eolCount; if (tokenDeltaLine === deltaLine) { + tokenDeltaLine += eolCount; // this token is on the line where the insertion is taking place if (eolCount === 0) { tokenStartCharacter += firstLineLength; @@ -384,6 +388,8 @@ export class SparseEncodedTokens implements IEncodedTokens { tokenStartCharacter = lastLineLength + (tokenStartCharacter - character); tokenEndCharacter = tokenStartCharacter + tokenLength; } + } else { + tokenDeltaLine += eolCount; } } @@ -527,9 +533,9 @@ export class MultilineTokens2 { const deletedBefore = -firstLineIndex; this.startLineNumber -= deletedBefore; - this.tokens.acceptDeleteRange(0, 0, lastLineIndex, range.endColumn - 1); + this.tokens.acceptDeleteRange(range.startColumn - 1, 0, 0, lastLineIndex, range.endColumn - 1); } else { - this.tokens.acceptDeleteRange(firstLineIndex, range.startColumn - 1, lastLineIndex, range.endColumn - 1); + this.tokens.acceptDeleteRange(0, firstLineIndex, range.startColumn - 1, lastLineIndex, range.endColumn - 1); } } diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index ef023724ae..058d3a2748 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -652,11 +652,15 @@ class ModelSemanticColoring extends Disposable { this._isDisposed = false; this._model = model; this._semanticStyling = stylingProvider; - this._fetchSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchSemanticTokensNow(), 500)); + this._fetchSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchSemanticTokensNow(), 300)); this._currentResponse = null; this._currentRequestCancellationTokenSource = null; - this._register(this._model.onDidChangeContent(e => this._fetchSemanticTokens.schedule())); + this._register(this._model.onDidChangeContent(e => { + if (!this._fetchSemanticTokens.isScheduled()) { + this._fetchSemanticTokens.schedule(); + } + })); this._register(SemanticTokensProviderRegistry.onDidChange(e => this._fetchSemanticTokens.schedule())); if (themeService) { // workaround for tests which use undefined... :/ @@ -887,7 +891,9 @@ class ModelSemanticColoring extends Disposable { } } - this._fetchSemanticTokens.schedule(); + if (!this._fetchSemanticTokens.isScheduled()) { + this._fetchSemanticTokens.schedule(); + } } this._model.setSemanticTokens(result); diff --git a/src/vs/editor/common/view/editorColorRegistry.ts b/src/vs/editor/common/view/editorColorRegistry.ts index b3db749997..6b30ef7d27 100644 --- a/src/vs/editor/common/view/editorColorRegistry.ts +++ b/src/vs/editor/common/view/editorColorRegistry.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { Color, RGBA } from 'vs/base/common/color'; -import { activeContrastBorder, editorBackground, editorForeground, registerColor, editorWarningForeground, editorInfoForeground, editorWarningBorder, editorInfoBorder, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { activeContrastBorder, editorBackground, editorForeground, registerColor, editorWarningForeground, editorInfoForeground, editorWarningBorder, editorInfoBorder, contrastBorder, editorFindMatchHighlight } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; /** @@ -15,7 +15,7 @@ export const editorLineHighlight = registerColor('editor.lineHighlightBackground export const editorLineHighlightBorder = registerColor('editor.lineHighlightBorder', { dark: '#282828', light: '#eeeeee', hc: '#f38518' }, nls.localize('lineHighlightBorderBox', 'Background color for the border around the line at the cursor position.')); export const editorRangeHighlight = registerColor('editor.rangeHighlightBackground', { dark: '#ffffff0b', light: '#fdff0033', hc: null }, nls.localize('rangeHighlight', 'Background color of highlighted ranges, like by quick open and find features. The color must not be opaque so as not to hide underlying decorations.'), true); export const editorRangeHighlightBorder = registerColor('editor.rangeHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('rangeHighlightBorder', 'Background color of the border around highlighted ranges.'), true); -export const editorSymbolHighlight = registerColor('editor.symbolHighlightBackground', { dark: editorRangeHighlight, light: editorRangeHighlight, hc: null }, nls.localize('symbolHighlight', 'Background color of highlighted symbol, like for go to definition or go next/previous symbol. The color must not be opaque so as not to hide underlying decorations.'), true); +export const editorSymbolHighlight = registerColor('editor.symbolHighlightBackground', { dark: editorFindMatchHighlight, light: editorFindMatchHighlight, hc: null }, nls.localize('symbolHighlight', 'Background color of highlighted symbol, like for go to definition or go next/previous symbol. The color must not be opaque so as not to hide underlying decorations.'), true); export const editorSymbolHighlightBorder = registerColor('editor.symbolHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('symbolHighlightBorder', 'Background color of the border around highlighted symbols.'), true); export const editorCursorForeground = registerColor('editorCursor.foreground', { dark: '#AEAFAD', light: Color.black, hc: Color.white }, nls.localize('caret', 'Color of the editor cursor.')); diff --git a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts index 0893c54610..bc9e43f5ac 100644 --- a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts @@ -116,7 +116,7 @@ export class BracketMatchingController extends Disposable implements editorCommo private _lastVersionId: number; private _decorations: string[]; private readonly _updateBracketsSoon: RunOnceScheduler; - private _matchBrackets: boolean; + private _matchBrackets: 'never' | 'near' | 'always'; constructor( editor: ICodeEditor @@ -132,7 +132,7 @@ export class BracketMatchingController extends Disposable implements editorCommo this._updateBracketsSoon.schedule(); this._register(editor.onDidChangeCursorPosition((e) => { - if (!this._matchBrackets) { + if (this._matchBrackets === 'never') { // Early exit if nothing needs to be done! // Leave some form of early exit check here if you wish to continue being a cursor position change listener ;) return; @@ -153,12 +153,13 @@ export class BracketMatchingController extends Disposable implements editorCommo this._updateBracketsSoon.schedule(); })); this._register(editor.onDidChangeConfiguration((e) => { - this._matchBrackets = this._editor.getOption(EditorOption.matchBrackets); - if (!this._matchBrackets && this._decorations.length > 0) { - // Remove existing decorations if bracket matching is off + if (e.hasChanged(EditorOption.matchBrackets)) { + this._matchBrackets = this._editor.getOption(EditorOption.matchBrackets); this._decorations = this._editor.deltaDecorations(this._decorations, []); + this._lastBracketsData = []; + this._lastVersionId = 0; + this._updateBracketsSoon.schedule(); } - this._updateBracketsSoon.schedule(); })); } @@ -262,7 +263,7 @@ export class BracketMatchingController extends Disposable implements editorCommo }); private _updateBrackets(): void { - if (!this._matchBrackets) { + if (this._matchBrackets === 'never') { return; } this._recomputeBrackets(); @@ -332,8 +333,8 @@ export class BracketMatchingController extends Disposable implements editorCommo } else { let brackets = model.matchBracket(position); let options = BracketMatchingController._DECORATION_OPTIONS_WITH_OVERVIEW_RULER; - if (!brackets) { - brackets = model.findEnclosingBrackets(position); + if (!brackets && this._matchBrackets === 'always') { + brackets = model.findEnclosingBrackets(position, 20 /* give at most 20ms to compute */); options = BracketMatchingController._DECORATION_OPTIONS_WITHOUT_OVERVIEW_RULER; } newData[newDataLen++] = new BracketsData(position, brackets, options); diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index 06b332c8a9..cc0334ed97 100644 --- a/src/vs/editor/contrib/contextmenu/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/contextmenu.ts @@ -148,19 +148,29 @@ export class ContextMenuController implements IEditorContribution { // translate them into other actions for (let group of groups) { const [, actions] = group; + let addedItems = 0; for (const action of actions) { if (action instanceof SubmenuItemAction) { const subActions = this._getMenuActions(model, action.item.submenu); if (subActions.length > 0) { result.push(new ContextSubMenu(action.label, subActions)); + addedItems++; } } else { result.push(action); + addedItems++; } } - result.push(new Separator()); + + if (addedItems) { + result.push(new Separator()); + } } - result.pop(); // remove last separator + + if (result.length) { + result.pop(); // remove last separator + } + return result; } diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 6383ed6186..7e664963c4 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -63,7 +63,7 @@ const PART_WIDTH = 275; const FIND_INPUT_AREA_WIDTH = PART_WIDTH - 54; let MAX_MATCHES_COUNT_WIDTH = 69; -let FIND_ALL_CONTROLS_WIDTH = 17/** Find Input margin-left */ + (MAX_MATCHES_COUNT_WIDTH + 3 + 1) /** Match Results */ + 23 /** Button */ * 4 + 2/** sash */; +// let FIND_ALL_CONTROLS_WIDTH = 17/** Find Input margin-left */ + (MAX_MATCHES_COUNT_WIDTH + 3 + 1) /** Match Results */ + 23 /** Button */ * 4 + 2/** sash */; const FIND_INPUT_AREA_HEIGHT = 33; // The height of Find Widget when Replace Input is not visible. const ctrlEnterReplaceAllWarningPromptedKey = 'ctrlEnterReplaceAll.windows.donotask'; @@ -706,10 +706,12 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas if (this._resized) { this._findInput.inputBox.layout(); - let findInputWidth = this._findInput.inputBox.width; + let findInputWidth = this._findInput.inputBox.element.clientWidth; if (findInputWidth > 0) { this._replaceInput.width = findInputWidth; } + } else if (this._isReplaceVisible) { + this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); } } @@ -1159,13 +1161,11 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas return; } - const inputBoxWidth = width - FIND_ALL_CONTROLS_WIDTH; const maxWidth = parseFloat(dom.getComputedStyle(this._domNode).maxWidth!) || 0; if (width > maxWidth) { return; } this._domNode.style.width = `${width}px`; - this._findInput.inputBox.width = inputBoxWidth; if (this._isReplaceVisible) { this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); } @@ -1197,10 +1197,8 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas */ } - const inputBoxWidth = width - FIND_ALL_CONTROLS_WIDTH; this._domNode.style.width = `${width}px`; - this._findInput.inputBox.width = inputBoxWidth; if (this._isReplaceVisible) { this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); } diff --git a/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts b/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts index 85b00f975e..a6bba6e922 100644 --- a/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts +++ b/src/vs/editor/contrib/gotoSymbol/symbolNavigation.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ReferencesModel, OneReference } from 'vs/editor/contrib/gotoSymbol/referencesModel'; -import { RawContextKey, IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -155,10 +155,7 @@ registerEditorCommand(new class extends EditorCommand { constructor() { super({ id: 'editor.gotoNextSymbolFromResult', - precondition: ContextKeyExpr.and( - ctxHasSymbols, - ContextKeyExpr.equals('config.editor.gotoLocation.multiple', 'goto') - ), + precondition: ctxHasSymbols, kbOpts: { weight: KeybindingWeight.EditorContrib, primary: KeyCode.F12 diff --git a/src/vs/editor/contrib/parameterHints/parameterHints.css b/src/vs/editor/contrib/parameterHints/parameterHints.css index 7420daafd3..2b03b69167 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHints.css +++ b/src/vs/editor/contrib/parameterHints/parameterHints.css @@ -34,6 +34,7 @@ .monaco-editor .parameter-hints-widget .body { display: flex; flex-direction: column; + min-height: 100%; } .monaco-editor .parameter-hints-widget .signature { @@ -72,6 +73,7 @@ .monaco-editor .parameter-hints-widget.multiple .controls { display: flex; + padding: 0 2px; } .monaco-editor .parameter-hints-widget.multiple .button { @@ -95,6 +97,7 @@ height: 12px; line-height: 12px; opacity: 0.5; + font-family: var(--monaco-monospace-font); } .monaco-editor .parameter-hints-widget .signature .parameter.active { diff --git a/src/vs/editor/test/common/model/tokensStore.test.ts b/src/vs/editor/test/common/model/tokensStore.test.ts new file mode 100644 index 0000000000..479951b6df --- /dev/null +++ b/src/vs/editor/test/common/model/tokensStore.test.ts @@ -0,0 +1,168 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { MultilineTokens2, SparseEncodedTokens } from 'vs/editor/common/model/tokensStore'; +import { Range } from 'vs/editor/common/core/range'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; +import { MetadataConsts, TokenMetadata } from 'vs/editor/common/modes'; + +suite('TokensStore', () => { + + const SEMANTIC_COLOR = 5; + + function parseTokensState(state: string[]): { text: string; tokens: MultilineTokens2; } { + let text: string[] = []; + let tokens: number[] = []; + let baseLine = 1; + for (let i = 0; i < state.length; i++) { + const line = state[i]; + + let startOffset = 0; + let lineText = ''; + while (true) { + const firstPipeOffset = line.indexOf('|', startOffset); + if (firstPipeOffset === -1) { + break; + } + const secondPipeOffset = line.indexOf('|', firstPipeOffset + 1); + if (secondPipeOffset === -1) { + break; + } + if (firstPipeOffset + 1 === secondPipeOffset) { + // skip || + lineText += line.substring(startOffset, secondPipeOffset + 1); + startOffset = secondPipeOffset + 1; + continue; + } + + lineText += line.substring(startOffset, firstPipeOffset); + const tokenStartCharacter = lineText.length; + const tokenLength = secondPipeOffset - firstPipeOffset - 1; + const metadata = (SEMANTIC_COLOR << MetadataConsts.FOREGROUND_OFFSET); + + if (tokens.length === 0) { + baseLine = i + 1; + } + tokens.push(i + 1 - baseLine, tokenStartCharacter, tokenStartCharacter + tokenLength, metadata); + + lineText += line.substr(firstPipeOffset + 1, tokenLength); + startOffset = secondPipeOffset + 1; + } + + lineText += line.substring(startOffset); + + text.push(lineText); + } + + return { + text: text.join('\n'), + tokens: new MultilineTokens2(baseLine, new SparseEncodedTokens(new Uint32Array(tokens))) + }; + } + + function extractState(model: TextModel): string[] { + let result: string[] = []; + for (let lineNumber = 1; lineNumber <= model.getLineCount(); lineNumber++) { + const lineTokens = model.getLineTokens(lineNumber); + const lineContent = model.getLineContent(lineNumber); + + let lineText = ''; + for (let i = 0; i < lineTokens.getCount(); i++) { + const tokenStartCharacter = lineTokens.getStartOffset(i); + const tokenEndCharacter = lineTokens.getEndOffset(i); + const metadata = lineTokens.getMetadata(i); + const color = TokenMetadata.getForeground(metadata); + const tokenText = lineContent.substring(tokenStartCharacter, tokenEndCharacter); + if (color === SEMANTIC_COLOR) { + lineText += `|${tokenText}|`; + } else { + lineText += tokenText; + } + } + + result.push(lineText); + } + return result; + } + + // function extractState + + function testTokensAdjustment(rawInitialState: string[], edits: IIdentifiedSingleEditOperation[], rawFinalState: string[]) { + const initialState = parseTokensState(rawInitialState); + const model = TextModel.createFromString(initialState.text); + model.setSemanticTokens([initialState.tokens]); + + model.applyEdits(edits); + + const actualState = extractState(model); + assert.deepEqual(actualState, rawFinalState); + + model.dispose(); + } + + test('issue #86303 - color shifting between different tokens', () => { + testTokensAdjustment( + [ + `import { |URI| } from 'vs/base/common/uri';`, + `const foo = |URI|.parse('hey');` + ], + [ + { range: new Range(2, 9, 2, 10), text: '' } + ], + [ + `import { |URI| } from 'vs/base/common/uri';`, + `const fo = |URI|.parse('hey');` + ] + ); + }); + + test('deleting a newline', () => { + testTokensAdjustment( + [ + `import { |URI| } from 'vs/base/common/uri';`, + `const foo = |URI|.parse('hey');` + ], + [ + { range: new Range(1, 42, 2, 1), text: '' } + ], + [ + `import { |URI| } from 'vs/base/common/uri';const foo = |URI|.parse('hey');` + ] + ); + }); + + test('inserting a newline', () => { + testTokensAdjustment( + [ + `import { |URI| } from 'vs/base/common/uri';const foo = |URI|.parse('hey');` + ], + [ + { range: new Range(1, 42, 1, 42), text: '\n' } + ], + [ + `import { |URI| } from 'vs/base/common/uri';`, + `const foo = |URI|.parse('hey');` + ] + ); + }); + + test('deleting a newline 2', () => { + testTokensAdjustment( + [ + `import { `, + ` |URI| } from 'vs/base/common/uri';const foo = |URI|.parse('hey');` + ], + [ + { range: new Range(1, 10, 2, 5), text: '' } + ], + [ + `import { |URI| } from 'vs/base/common/uri';const foo = |URI|.parse('hey');` + ] + ); + }); + +}); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 626de975fd..55316f5507 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2880,9 +2880,9 @@ declare namespace monaco.editor { showFoldingControls?: 'always' | 'mouseover'; /** * Enable highlighting of matching brackets. - * Defaults to true. + * Defaults to 'always'. */ - matchBrackets?: boolean; + matchBrackets?: 'never' | 'near' | 'always'; /** * Enable rendering of whitespace. * Defaults to none. diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 23a2a5fb4b..019ed8e6fd 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -819,3 +819,18 @@ export function etag(stat: { mtime: number | undefined, size: number | undefined return stat.mtime.toString(29) + stat.size.toString(31); } + + +export function whenProviderRegistered(file: URI, fileService: IFileService): Promise { + if (fileService.canHandleResource(URI.from({ scheme: file.scheme }))) { + return Promise.resolve(); + } + return new Promise((c, e) => { + const disposable = fileService.onDidChangeFileSystemProviderRegistrations(e => { + if (e.scheme === file.scheme && e.added) { + disposable.dispose(); + c(); + } + }); + }); +} diff --git a/src/vs/platform/log/common/fileLogService.ts b/src/vs/platform/log/common/fileLogService.ts index d7376e06f6..625fbdd42d 100644 --- a/src/vs/platform/log/common/fileLogService.ts +++ b/src/vs/platform/log/common/fileLogService.ts @@ -5,12 +5,13 @@ import { ILogService, LogLevel, AbstractLogService, ILoggerService, ILogger } from 'vs/platform/log/common/log'; import { URI } from 'vs/base/common/uri'; -import { IFileService } from 'vs/platform/files/common/files'; +import { IFileService, whenProviderRegistered } from 'vs/platform/files/common/files'; import { Queue } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { dirname, joinPath, basename } from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { BufferLogService } from 'vs/platform/log/common/bufferLog'; const MAX_FILE_SIZE = 1024 * 1024 * 5; @@ -163,6 +164,7 @@ export class FileLoggerService extends Disposable implements ILoggerService { constructor( @ILogService private logService: ILogService, @IInstantiationService private instantiationService: IInstantiationService, + @IFileService private fileService: IFileService, ) { super(); this._register(logService.onDidChangeLogLevel(level => this.loggers.forEach(logger => logger.setLevel(level)))); @@ -171,8 +173,9 @@ export class FileLoggerService extends Disposable implements ILoggerService { getLogger(resource: URI): ILogger { let logger = this.loggers.get(resource.toString()); if (!logger) { - logger = this.instantiationService.createInstance(FileLogService, basename(resource), resource, this.logService.getLevel()); + logger = new BufferLogService, this.logService.getLevel(); this.loggers.set(resource.toString(), logger); + whenProviderRegistered(resource, this.fileService).then(() => (logger).logger = this.instantiationService.createInstance(FileLogService, basename(resource), resource, this.logService.getLevel())); } return logger; } diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index d97e6ea1c0..db2f038f1b 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -306,8 +306,8 @@ export const editorFindRangeHighlightBorder = registerColor('editor.findRangeHig * * Distinct from normal editor find match to allow for better differentiation */ -export const searchEditorFindMatch = registerColor('searchEditor.findMatchBackground', { light: transparent(editorFindMatchHighlight, 0.5), dark: transparent(editorFindMatchHighlight, 0.5), hc: editorFindMatchHighlight }, nls.localize('searchEditor.queryMatch', "Color of the Search Editor query matches.")); -export const searchEditorFindMatchBorder = registerColor('searchEditor.findMatchBorder', { light: transparent(editorFindMatchHighlightBorder, 0.5), dark: transparent(editorFindMatchHighlightBorder, 0.5), hc: editorFindMatchHighlightBorder }, nls.localize('searchEditor.editorFindMatchBorder', "Border color of the Search Editor query matches.")); +export const searchEditorFindMatch = registerColor('searchEditor.findMatchBackground', { light: transparent(editorFindMatchHighlight, 0.66), dark: transparent(editorFindMatchHighlight, 0.66), hc: editorFindMatchHighlight }, nls.localize('searchEditor.queryMatch', "Color of the Search Editor query matches.")); +export const searchEditorFindMatchBorder = registerColor('searchEditor.findMatchBorder', { light: transparent(editorFindMatchHighlightBorder, 0.66), dark: transparent(editorFindMatchHighlightBorder, 0.66), hc: editorFindMatchHighlightBorder }, nls.localize('searchEditor.editorFindMatchBorder', "Border color of the Search Editor query matches.")); /** * Editor hover diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 7beb2a2b54..974d6b91f7 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -320,40 +320,45 @@ export function getTokenClassificationRegistry(): ITokenClassificationRegistry { return tokenClassificationRegistry; } -export const comments = registerTokenType('comments', nls.localize('comments', "Style for comments."), [['comment']]); -export const strings = registerTokenType('strings', nls.localize('strings', "Style for strings."), [['string']]); -export const keywords = registerTokenType('keywords', nls.localize('keywords', "Style for keywords."), [['keyword.control']]); -export const numbers = registerTokenType('numbers', nls.localize('numbers', "Style for numbers."), [['constant.numeric']]); -export const regexp = registerTokenType('regexp', nls.localize('regexp', "Style for expressions."), [['constant.regexp']]); -export const operators = registerTokenType('operators', nls.localize('operator', "Style for operators."), [['keyword.operator']]); +// default token types -export const namespaces = registerTokenType('namespaces', nls.localize('namespace', "Style for namespaces."), [['entity.name.namespace']]); +registerTokenType('comment', nls.localize('comment', "Style for comments."), [['comment']]); +registerTokenType('string', nls.localize('string', "Style for strings."), [['string']]); +registerTokenType('keyword', nls.localize('keyword', "Style for keywords."), [['keyword.control']]); +registerTokenType('number', nls.localize('number', "Style for numbers."), [['constant.numeric']]); +registerTokenType('regexp', nls.localize('regexp', "Style for expressions."), [['constant.regexp']]); +registerTokenType('operator', nls.localize('operator', "Style for operators."), [['keyword.operator']]); -export const types = registerTokenType('types', nls.localize('types', "Style for types."), [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']]); -export const structs = registerTokenType('structs', nls.localize('struct', "Style for structs."), [['storage.type.struct']], types); -export const classes = registerTokenType('classes', nls.localize('class', "Style for classes."), [['entity.name.class']], types); -export const interfaces = registerTokenType('interfaces', nls.localize('interface', "Style for interfaces."), undefined, types); -export const enums = registerTokenType('enums', nls.localize('enum', "Style for enums."), undefined, types); -export const parameterTypes = registerTokenType('parameterTypes', nls.localize('parameterType', "Style for parameter types."), undefined, types); +registerTokenType('namespace', nls.localize('namespace', "Style for namespaces."), [['entity.name.namespace']]); -export const functions = registerTokenType('functions', nls.localize('functions', "Style for functions"), [['entity.name.function'], ['support.function']]); -export const macros = registerTokenType('macros', nls.localize('macro', "Style for macros."), undefined, functions); +registerTokenType('type', nls.localize('type', "Style for types."), [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']]); +registerTokenType('struct', nls.localize('struct', "Style for structs."), [['storage.type.struct']], 'type'); +registerTokenType('class', nls.localize('class', "Style for classes."), [['entity.name.class']], 'type'); +registerTokenType('interface', nls.localize('interface', "Style for interfaces."), undefined, 'type'); +registerTokenType('enum', nls.localize('enum', "Style for enums."), undefined, 'type'); +registerTokenType('parameterType', nls.localize('parameterType', "Style for parameter types."), undefined, 'type'); -export const variables = registerTokenType('variables', nls.localize('variables', "Style for variables."), [['variable'], ['entity.name.variable']]); -export const constants = registerTokenType('constants', nls.localize('constants', "Style for constants."), undefined, variables); -export const parameters = registerTokenType('parameters', nls.localize('parameters', "Style for parameters."), undefined, variables); -export const property = registerTokenType('properties', nls.localize('properties', "Style for properties."), undefined, variables); +registerTokenType('function', nls.localize('function', "Style for functions"), [['entity.name.function'], ['support.function']]); +registerTokenType('macro', nls.localize('macro', "Style for macros."), undefined, 'function'); -export const labels = registerTokenType('labels', nls.localize('labels', "Style for labels. "), undefined); +registerTokenType('variable', nls.localize('variable', "Style for variables."), [['variable'], ['entity.name.variable']]); +registerTokenType('constant', nls.localize('constant', "Style for constants."), undefined, 'variable'); +registerTokenType('parameter', nls.localize('parameter', "Style for parameters."), undefined, 'variable'); +registerTokenType('property', nls.localize('propertie', "Style for properties."), undefined, 'variable'); + +registerTokenType('label', nls.localize('labels', "Style for labels. "), undefined); + +// default token modifiers + +registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined); +registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined); +registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined); +registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined); +registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined); +registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined); +registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined); +registerTokenModifier('async', nls.localize('async', "Style to use for symbols that are async."), undefined); -export const m_declaration = registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined); -export const m_documentation = registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined); -export const m_member = registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined); -export const m_static = registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined); -export const m_abstract = registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined); -export const m_deprecated = registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined); -export const m_modification = registerTokenModifier('modification', nls.localize('modification', "Style to use for write accesses."), undefined); -export const m_async = registerTokenModifier('async', nls.localize('async', "Style to use for symbols that are async."), undefined); function bitCount(u: number) { // https://blogs.msdn.microsoft.com/jeuge/2005/06/08/bit-fiddling-3/ diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index f495c95359..efb3f52e0b 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -18,6 +18,7 @@ import { startsWith } from 'vs/base/common/strings'; import { IFileService } from 'vs/platform/files/common/files'; import { Queue } from 'vs/base/common/async'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { localize } from 'vs/nls'; export interface ISyncPreviewResult { readonly added: ISyncExtension[]; @@ -26,6 +27,10 @@ export interface ISyncPreviewResult { readonly remote: ISyncExtension[] | null; } +interface ILastSyncUserData extends IUserData { + skippedExtensions: ISyncExtension[] | undefined; +} + export class ExtensionsSynchroniser extends Disposable implements ISynchroniser { private static EXTERNAL_USER_DATA_EXTENSIONS_KEY: string = 'extensions'; @@ -122,14 +127,16 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser private async doSync(): Promise { const lastSyncData = await this.getLastSyncUserData(); - let remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData); + const lastSyncExtensions: ISyncExtension[] | null = lastSyncData ? JSON.parse(lastSyncData.content!) : null; + let skippedExtensions: ISyncExtension[] = lastSyncData ? lastSyncData.skippedExtensions || [] : []; - const lastSyncExtensions: ISyncExtension[] = lastSyncData ? JSON.parse(lastSyncData.content!) : null; + let remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData); const remoteExtensions: ISyncExtension[] = remoteData.content ? JSON.parse(remoteData.content) : null; + const localExtensions = await this.getLocalExtensions(); this.logService.trace('Extensions: Merging remote extensions with local extensions...'); - const { added, removed, updated, remote } = this.merge(localExtensions, remoteExtensions, lastSyncExtensions); + const { added, removed, updated, remote } = this.merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions); if (!added.length && !removed.length && !updated.length && !remote) { this.logService.trace('Extensions: No changes found during synchronizing extensions.'); @@ -137,7 +144,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser if (added.length || removed.length || updated.length) { this.logService.info('Extensions: Updating local extensions...'); - await this.updateLocalExtensions(added, removed, updated); + skippedExtensions = await this.updateLocalExtensions(added, removed, updated, skippedExtensions); } if (remote) { @@ -151,7 +158,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser ) { // update last sync this.logService.info('Extensions: Updating last synchronised extensions...'); - await this.updateLastSyncValue(remoteData); + await this.updateLastSyncValue({ ...remoteData, skippedExtensions }); } } @@ -161,7 +168,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser * - Overwrite local with remote changes. Removed, Added, Updated. * - Update remote with those local extension which are newly added or updated or removed and untouched in remote. */ - private merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null): { added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], remote: ISyncExtension[] | null } { + private merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null, skippedExtensions: ISyncExtension[]): { added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], remote: ISyncExtension[] | null } { const ignoredExtensions = this.configurationService.getValue('sync.ignoredExtensions') || []; // First time sync if (!remoteExtensions) { @@ -187,6 +194,7 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map()); const newRemoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map()); const lastSyncExtensionsMap = lastSyncExtensions ? lastSyncExtensions.reduce(addExtensionToMap, new Map()) : null; + const skippedExtensionsMap = skippedExtensions.reduce(addExtensionToMap, new Map()); const ignoredExtensionsSet = ignoredExtensions.reduce((set, id) => { const uuid = uuids.get(id.toLowerCase()); return set.add(uuid ? `uuid:${uuid}` : `id:${id.toLowerCase()}`); @@ -273,8 +281,8 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser // Locally removed extensions for (const key of values(baseToLocal.removed)) { - // If not updated in remote - if (!baseToRemote.updated.has(key)) { + // If not skipped and not updated in remote + if (!skippedExtensionsMap.has(key) && !baseToRemote.updated.has(key)) { newRemoteExtensionsMap.delete(key); } } @@ -308,13 +316,17 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser return { added, removed, updated }; } - private async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[]): Promise { + private async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], skippedExtensions: ISyncExtension[]): Promise { + const removeFromSkipped: IExtensionIdentifier[] = []; + const addToSkipped: ISyncExtension[] = []; + if (removed.length) { const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); const extensionsToRemove = installedExtensions.filter(({ identifier }) => removed.some(r => areSameExtensions(identifier, r))); - await Promise.all(extensionsToRemove.map(e => { - this.logService.info('Extensions: Removing local extension.', e.identifier.id); - return this.extensionManagementService.uninstall(e); + await Promise.all(extensionsToRemove.map(async extensionToRemove => { + this.logService.info('Extensions: Removing local extension.', extensionToRemove.identifier.id); + await this.extensionManagementService.uninstall(extensionToRemove); + removeFromSkipped.push(extensionToRemove.identifier); })); } @@ -323,18 +335,41 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier, e.version); if (extension) { this.logService.info('Extensions: Installing local extension.', e.identifier.id, extension.version); - await this.extensionManagementService.installFromGallery(extension); + try { + await this.extensionManagementService.installFromGallery(extension); + removeFromSkipped.push(extension.identifier); + } catch (error) { + addToSkipped.push(e); + this.logService.error(error); + this.logService.info(localize('skip extension', "Skipping synchronising extension {0}", extension.displayName || extension.identifier.id)); + } + } else { + addToSkipped.push(e); } })); } + + const newSkippedExtensions: ISyncExtension[] = []; + for (const skippedExtension of skippedExtensions) { + if (!removeFromSkipped.some(e => areSameExtensions(e, skippedExtension.identifier))) { + newSkippedExtensions.push(skippedExtension); + } + } + for (const skippedExtension of addToSkipped) { + if (!newSkippedExtensions.some(e => areSameExtensions(e.identifier, skippedExtension.identifier))) { + newSkippedExtensions.push(skippedExtension); + } + } + return newSkippedExtensions; } private async getLocalExtensions(): Promise { const installedExtensions = await this.extensionManagementService.getInstalled(ExtensionType.User); - return installedExtensions.map(({ identifier }) => ({ identifier, enabled: true })); + return installedExtensions + .map(({ identifier }) => ({ identifier, enabled: true })); } - private async getLastSyncUserData(): Promise { + private async getLastSyncUserData(): Promise { try { const content = await this.fileService.readFile(this.lastSyncExtensionsResource); return JSON.parse(content.value.toString()); @@ -343,14 +378,14 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser } } + private async updateLastSyncValue(lastSyncUserData: ILastSyncUserData): Promise { + await this.fileService.writeFile(this.lastSyncExtensionsResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData))); + } + private async writeToRemote(extensions: ISyncExtension[], ref: string | null): Promise { const content = JSON.stringify(extensions); ref = await this.userDataSyncStoreService.write(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, content, ref); return { content, ref }; } - private async updateLastSyncValue(remoteUserData: IUserData): Promise { - await this.fileService.writeFile(this.lastSyncExtensionsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); - } - } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 933f9e39cb..2b57fe82d8 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -11,10 +11,10 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; -import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; -import { joinPath } from 'vs/base/common/resources'; +import { joinPath, dirname } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; import { OS, OperatingSystem } from 'vs/base/common/platform'; @@ -46,7 +46,6 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser private _onDidChangStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangStatus.event; - private readonly throttledDelayer: ThrottledDelayer; private _onDidChangeLocal: Emitter = this._register(new Emitter()); readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; @@ -62,24 +61,8 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser ) { super(); this.lastSyncKeybindingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncKeybindings.json'); - this.throttledDelayer = this._register(new ThrottledDelayer(500)); - this._register(this.fileService.watch(this.environmentService.keybindingsResource)); - this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.keybindingsResource))(() => this.throttledDelayer.trigger(() => this.onDidChangeKeybindings()))); - } - - private async onDidChangeKeybindings(): Promise { - const localFileContent = await this.getLocalContent(); - const lastSyncData = await this.getLastSyncUserData(); - if (localFileContent && lastSyncData) { - if (localFileContent.value.toString() !== lastSyncData.content) { - this._onDidChangeLocal.fire(); - return; - } - } - if (!localFileContent || !lastSyncData) { - this._onDidChangeLocal.fire(); - return; - } + this._register(this.fileService.watch(dirname(this.environmentService.keybindingsResource))); + this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.keybindingsResource))(() => this._onDidChangeLocal.fire())); } private setStatus(status: SyncStatus): void { diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index ed78d72517..a9bd2cd61f 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -10,10 +10,10 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; -import { CancelablePromise, createCancelablePromise, ThrottledDelayer } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; -import { joinPath } from 'vs/base/common/resources'; +import { joinPath, dirname } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { startsWith } from 'vs/base/common/strings'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -37,7 +37,6 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { private _onDidChangStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangStatus.event; - private readonly throttledDelayer: ThrottledDelayer; private _onDidChangeLocal: Emitter = this._register(new Emitter()); readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; @@ -53,23 +52,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser { ) { super(); this.lastSyncSettingsResource = joinPath(this.environmentService.userRoamingDataHome, '.lastSyncSettings.json'); - this.throttledDelayer = this._register(new ThrottledDelayer(500)); - this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.settingsResource))(() => this.throttledDelayer.trigger(() => this.onDidChangeSettings()))); - } - - private async onDidChangeSettings(): Promise { - const localFileContent = await this.getLocalFileContent(); - const lastSyncData = await this.getLastSyncUserData(); - if (localFileContent && lastSyncData) { - if (localFileContent.value.toString() !== lastSyncData.content) { - this._onDidChangeLocal.fire(); - return; - } - } - if (!localFileContent || !lastSyncData) { - this._onDidChangeLocal.fire(); - return; - } + this._register(this.fileService.watch(dirname(this.environmentService.settingsResource))); + this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.settingsResource))(() => this._onDidChangeLocal.fire())); } private setStatus(status: SyncStatus): void { diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 6f181b922a..2af0214a4d 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8084,6 +8084,172 @@ declare module 'vscode' { waitUntil(thenable: Thenable): void; } + /** + * An event that is fired when files are going to be created. + * + * To make modifications to the workspace before the files are created, + * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a + * thenable that resolves to a [workspace edit](#WorkspaceEdit). + */ + export interface FileWillCreateEvent { + + /** + * The files that are going to be created. + */ + readonly files: ReadonlyArray; + + /** + * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillCreateFiles(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event until the provided thenable resolves. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } + + /** + * An event that is fired after files are created. + */ + export interface FileCreateEvent { + + /** + * The files that got created. + */ + readonly files: ReadonlyArray; + } + + /** + * An event that is fired when files are going to be deleted. + * + * To make modifications to the workspace before the files are deleted, + * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a + * thenable that resolves to a [workspace edit](#WorkspaceEdit). + */ + export interface FileWillDeleteEvent { + + /** + * The files that are going to be deleted. + */ + readonly files: ReadonlyArray; + + /** + * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillCreateFiles(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event until the provided thenable resolves. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } + + /** + * An event that is fired after files are deleted. + */ + export interface FileDeleteEvent { + + /** + * The files that got deleted. + */ + readonly files: ReadonlyArray; + } + + /** + * An event that is fired when files are going to be renamed. + * + * To make modifications to the workspace before the files are renamed, + * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a + * thenable that resolves to a [workspace edit](#WorkspaceEdit). + */ + export interface FileWillRenameEvent { + + /** + * The files that are going to be renamed. + */ + readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>; + + /** + * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillCreateFiles(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event until the provided thenable resolves. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } + + /** + * An event that is fired after files are renamed. + */ + export interface FileRenameEvent { + + /** + * The files that got renamed. + */ + readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>; + } + + /** * An event describing a change to the set of [workspace folders](#workspace.workspaceFolders). */ @@ -8433,6 +8599,76 @@ declare module 'vscode' { */ export const onDidSaveTextDocument: Event; + /** + * An event that is emitted when files are being created. + * + * *Note 1:* This event is triggered by user gestures, like creating a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api. This event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When this event is fired, edits to files thare are being created cannot be applied. + */ + export const onWillCreateFiles: Event; + + /** + * An event that is emitted when files have been created. + * + * *Note:* This event is triggered by user gestures, like creating a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + */ + export const onDidCreateFiles: Event; + + /** + * An event that is emitted when files are being deleted. + * + * *Note 1:* This event is triggered by user gestures, like deleting a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When deleting a folder with children only one event is fired. + */ + export const onWillDeleteFiles: Event; + + /** + * An event that is emitted when files have been deleted. + * + * *Note 1:* This event is triggered by user gestures, like deleting a file from the + * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When deleting a folder with children only one event is fired. + */ + export const onDidDeleteFiles: Event; + + /** + * An event that is emitted when files are being renamed. + * + * *Note 1:* This event is triggered by user gestures, like renaming a file from the + * explorer, and from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When renaming a folder with children only one event is fired. + */ + export const onWillRenameFiles: Event; + + /** + * An event that is emitted when files have been renamed. + * + * *Note 1:* This event is triggered by user gestures, like renaming a file from the + * explorer, and from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when + * files change on disk, e.g triggered by another application, or when using the + * [`workspace.fs`](#FileSystem)-api. + * + * *Note 2:* When renaming a folder with children only one event is fired. + */ + export const onDidRenameFiles: Event; + /** * Get a workspace configuration object. * diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 81c5ad7fae..f15a39e735 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -68,7 +68,7 @@ declare module 'vscode' { //#endregion - //#region Alex - semantic tokens + //#region Semantic tokens: https://github.com/microsoft/vscode/issues/86415 export class SemanticTokensLegend { public readonly tokenTypes: string[]; @@ -83,15 +83,13 @@ declare module 'vscode' { build(): Uint32Array; } - /** - * A certain token (at index `i` is encoded using 5 uint32 integers): - * - at index `5*i` - `deltaLine`: token line number, relative to `SemanticColoringArea.line` - * - at index `5*i+1` - `deltaStart`: token start character offset inside the line (relative to 0 or the previous token if they are on the same line) - * - at index `5*i+2` - `length`: the length of the token - * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticColoringLegend.tokenTypes` - * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticColoringLegend.tokenModifiers` - */ export class SemanticTokens { + /** + * The result id of the tokens. + * + * On a next call to `provideSemanticTokens`, if VS Code still holds in memory this result, + * the result id will be passed in as `SemanticTokensRequestOptions.previousResultId`. + */ readonly resultId?: string; readonly data: Uint32Array; @@ -99,6 +97,12 @@ declare module 'vscode' { } export class SemanticTokensEdits { + /** + * The result id of the tokens. + * + * On a next call to `provideSemanticTokens`, if VS Code still holds in memory this result, + * the result id will be passed in as `SemanticTokensRequestOptions.previousResultId`. + */ readonly resultId?: string; readonly edits: SemanticTokensEdit[]; @@ -115,6 +119,11 @@ declare module 'vscode' { export interface SemanticTokensRequestOptions { readonly ranges?: readonly Range[]; + /** + * The previous result id that the editor still holds in memory. + * + * Only when this is set it is safe for a `SemanticTokensProvider` to return `SemanticTokensEdits`. + */ readonly previousResultId?: string; } @@ -123,6 +132,102 @@ declare module 'vscode' { * semantic tokens. */ export interface SemanticTokensProvider { + /** + * A file can contain many tokens, perhaps even hundreds of thousands tokens. Therefore, to improve + * the memory consumption around describing semantic tokens, we have decided to avoid allocating objects + * and we have decided to represent tokens from a file as an array of integers. + * + * + * In short, each token takes 5 integers to represent, so a specific token i in the file consists of the following fields: + * - at index `5*i` - `deltaLine`: token line number, relative to the previous token + * - at index `5*i+1` - `deltaStart`: token start character, relative to the previous token (relative to 0 or the previous token's start if they are on the same line) + * - at index `5*i+2` - `length`: the length of the token. A token cannot be multiline. + * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticTokensLegend.tokenTypes` + * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticTokensLegend.tokenModifiers` + * + * + * Here is an example for encoding a file with 3 tokens: + * ``` + * [ { line: 2, startChar: 5, length: 3, tokenType: "properties", tokenModifiers: ["private", "static"] }, + * { line: 2, startChar: 10, length: 4, tokenType: "types", tokenModifiers: [] }, + * { line: 5, startChar: 2, length: 7, tokenType: "classes", tokenModifiers: [] } ] + * ``` + * + * 1. First of all, a legend must be devised. This legend must be provided up-front and capture all possible token types. + * For this example, we will choose the following legend which is passed in when registering the provider: + * ``` + * { tokenTypes: ['', 'properties', 'types', 'classes'], + * tokenModifiers: ['', 'private', 'static'] } + * ``` + * + * 2. The first transformation is to encode `tokenType` and `tokenModifiers` as integers using the legend. Token types are looked + * up by index, so a `tokenType` value of `1` means `tokenTypes[1]`. Multiple token modifiers can be set by using bit flags, + * so a `tokenModifier` value of `6` is first viewed as binary `0b110`, which means `[tokenModifiers[1], tokenModifiers[2]]` because + * bits 1 and 2 are set. Using this legend, the tokens now are: + * ``` + * [ { line: 2, startChar: 5, length: 3, tokenType: 1, tokenModifiers: 6 }, // 6 is 0b110 + * { line: 2, startChar: 10, length: 4, tokenType: 2, tokenModifiers: 0 }, + * { line: 5, startChar: 2, length: 7, tokenType: 3, tokenModifiers: 0 } ] + * ``` + * + * 3. Then, we will encode each token relative to the previous token in the file: + * ``` + * [ { deltaLine: 2, deltaStartChar: 5, length: 3, tokenType: 1, tokenModifiers: 6 }, + * // this token is on the same line as the first one, so the startChar is made relative + * { deltaLine: 0, deltaStartChar: 5, length: 4, tokenType: 2, tokenModifiers: 0 }, + * // this token is on a different line than the second one, so the startChar remains unchanged + * { deltaLine: 3, deltaStartChar: 2, length: 7, tokenType: 3, tokenModifiers: 0 } ] + * ``` + * + * 4. Finally, the integers are organized in a single array, which is a memory friendly representation: + * ``` + * // 1st token, 2nd token, 3rd token + * [ 2,5,3,1,6, 0,5,4,2,0, 3,2,7,3,0 ] + * ``` + * + * In principle, each call to `provideSemanticTokens` expects a complete representations of the semantic tokens. + * It is possible to simply return all the tokens at each call. + * + * But oftentimes, a small edit in the file will result in a small change to the above delta-based represented tokens. + * (In fact, that is why the above tokens are delta-encoded relative to their corresponding previous tokens). + * In such a case, if VS Code passes in the previous result id, it is possible for an advanced tokenization provider + * to return a delta to the integers array. + * + * To continue with the previous example, suppose a new line has been pressed at the beginning of the file, such that + * all the tokens are now one line lower, and that a new token has appeared since the last result on line 4. + * For example, the tokens might look like: + * ``` + * [ { line: 3, startChar: 5, length: 3, tokenType: "properties", tokenModifiers: ["private", "static"] }, + * { line: 3, startChar: 10, length: 4, tokenType: "types", tokenModifiers: [] }, + * { line: 4, startChar: 3, length: 5, tokenType: "properties", tokenModifiers: ["static"] }, + * { line: 6, startChar: 2, length: 7, tokenType: "classes", tokenModifiers: [] } ] + * ``` + * + * The integer encoding of all new tokens would be: + * ``` + * [ 3,5,3,1,6, 0,5,4,2,0, 1,3,5,1,2, 2,2,7,3,0 ] + * ``` + * + * A smart tokens provider can return a `resultId` to `SemanticTokens`. Then, if the editor still has in memory the previous + * result, the editor will pass in options the previous result id at `SemanticTokensRequestOptions.previousResultId`. Only when + * the editor passes in the previous result id, it is safe and smart for a smart tokens provider can compute a diff from the + * previous result to the new result. + * + * *NOTE*: It is illegal to return `SemanticTokensEdits` if `options.previousResultId` is not set! + * + * ``` + * [ 2,5,3,1,6, 0,5,4,2,0, 3,2,7,3,0 ] + * [ 3,5,3,1,6, 0,5,4,2,0, 1,3,5,1,2, 2,2,7,3,0 ] + * ``` + * and return as simple integer edits the diff: + * ``` + * { edits: [ + * { start: 0, deleteCount: 1, data: [3] } // replace integer at offset 0 with 3 + * { start: 10, deleteCount: 1, data: [1,3,5,1,2,2] } // replace integer at offset 10 with [1,3,5,1,2,2] + * ]} + * ``` + * All indices expressed in the returned diff represent indices in the old result array, so they all refer to the previous result state. + */ provideSemanticTokens(document: TextDocument, options: SemanticTokensRequestOptions, token: CancellationToken): ProviderResult; } @@ -171,7 +276,7 @@ declare module 'vscode' { //#endregion - //#region Rob: search provider + //#region TextSearchProvider: https://github.com/microsoft/vscode/issues/59921 /** * The parameters of a query for text search. @@ -315,32 +420,6 @@ declare module 'vscode' { limitHit?: boolean; } - /** - * The parameters of a query for file search. - */ - export interface FileSearchQuery { - /** - * The search pattern to match against file paths. - */ - pattern: string; - } - - /** - * Options that apply to file search. - */ - export interface FileSearchOptions extends SearchOptions { - /** - * The maximum number of results to be returned. - */ - maxResults?: number; - - /** - * A CancellationToken that represents the session for this search query. If the provider chooses to, this object can be used as the key for a cache, - * and searches with the same session object can search the same cache. When the token is cancelled, the session is complete and the cache can be cleared. - */ - session?: CancellationToken; - } - /** * A preview of the text result. */ @@ -400,6 +479,50 @@ declare module 'vscode' { export type TextSearchResult = TextSearchMatch | TextSearchContext; + /** + * A TextSearchProvider provides search results for text results inside files in the workspace. + */ + export interface TextSearchProvider { + /** + * Provide results that match the given text pattern. + * @param query The parameters for this query. + * @param options A set of options to consider while searching. + * @param progress A progress callback that must be invoked for all results. + * @param token A cancellation token. + */ + provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): ProviderResult; + } + + //#endregion + + //#region FileSearchProvider: https://github.com/microsoft/vscode/issues/73524 + + /** + * The parameters of a query for file search. + */ + export interface FileSearchQuery { + /** + * The search pattern to match against file paths. + */ + pattern: string; + } + + /** + * Options that apply to file search. + */ + export interface FileSearchOptions extends SearchOptions { + /** + * The maximum number of results to be returned. + */ + maxResults?: number; + + /** + * A CancellationToken that represents the session for this search query. If the provider chooses to, this object can be used as the key for a cache, + * and searches with the same session object can search the same cache. When the token is cancelled, the session is complete and the cache can be cleared. + */ + session?: CancellationToken; + } + /** * A FileSearchProvider provides search results for files in the given folder that match a query string. It can be invoked by quickopen or other extensions. * @@ -419,20 +542,34 @@ declare module 'vscode' { provideFileSearchResults(query: FileSearchQuery, options: FileSearchOptions, token: CancellationToken): ProviderResult; } - /** - * A TextSearchProvider provides search results for text results inside files in the workspace. - */ - export interface TextSearchProvider { + export namespace workspace { /** - * Provide results that match the given text pattern. - * @param query The parameters for this query. - * @param options A set of options to consider while searching. - * @param progress A progress callback that must be invoked for all results. - * @param token A cancellation token. + * Register a search provider. + * + * Only one provider can be registered per scheme. + * + * @param scheme The provider will be invoked for workspace folders that have this file scheme. + * @param provider The provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. */ - provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): ProviderResult; + export function registerFileSearchProvider(scheme: string, provider: FileSearchProvider): Disposable; + + /** + * Register a text search provider. + * + * Only one provider can be registered per scheme. + * + * @param scheme The provider will be invoked for workspace folders that have this file scheme. + * @param provider The provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerTextSearchProvider(scheme: string, provider: TextSearchProvider): Disposable; } + //#endregion + + //#region findTextInFiles: https://github.com/microsoft/vscode/issues/59924 + /** * Options that can be set on a findTextInFiles search. */ @@ -497,28 +634,6 @@ declare module 'vscode' { } export namespace workspace { - /** - * Register a search provider. - * - * Only one provider can be registered per scheme. - * - * @param scheme The provider will be invoked for workspace folders that have this file scheme. - * @param provider The provider. - * @return A [disposable](#Disposable) that unregisters this provider when being disposed. - */ - export function registerFileSearchProvider(scheme: string, provider: FileSearchProvider): Disposable; - - /** - * Register a text search provider. - * - * Only one provider can be registered per scheme. - * - * @param scheme The provider will be invoked for workspace folders that have this file scheme. - * @param provider The provider. - * @return A [disposable](#Disposable) that unregisters this provider when being disposed. - */ - export function registerTextSearchProvider(scheme: string, provider: TextSearchProvider): Disposable; - /** * Search text in files across all [workspace folders](#workspace.workspaceFolders) in the workspace. * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words. @@ -647,7 +762,7 @@ declare module 'vscode' { //#endregion - //#region Rob, Matt: logging + //#region LogLevel: https://github.com/microsoft/vscode/issues/85992 /** * The severity level of a log message @@ -869,247 +984,6 @@ declare module 'vscode' { //#endregion - //#region mjbvz,joh: https://github.com/Microsoft/vscode/issues/43768 - - /** - * An event that is fired when files are going to be created. - * - * To make modifications to the workspace before the files are created, - * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a - * thenable that resolves to a [workspace edit](#WorkspaceEdit). - */ - export interface FileWillCreateEvent { - - /** - * The files that are going to be created. - */ - readonly files: ReadonlyArray; - - /** - * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). - * - * *Note:* This function can only be called during event dispatch and not - * in an asynchronous manner: - * - * ```ts - * workspace.onWillCreateFiles(event => { - * // async, will *throw* an error - * setTimeout(() => event.waitUntil(promise)); - * - * // sync, OK - * event.waitUntil(promise); - * }) - * ``` - * - * @param thenable A thenable that delays saving. - */ - waitUntil(thenable: Thenable): void; - - /** - * Allows to pause the event until the provided thenable resolves. - * - * *Note:* This function can only be called during event dispatch. - * - * @param thenable A thenable that delays saving. - */ - waitUntil(thenable: Thenable): void; - } - - /** - * An event that is fired after files are created. - */ - export interface FileCreateEvent { - - /** - * The files that got created. - */ - readonly files: ReadonlyArray; - } - - /** - * An event that is fired when files are going to be deleted. - * - * To make modifications to the workspace before the files are deleted, - * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a - * thenable that resolves to a [workspace edit](#WorkspaceEdit). - */ - export interface FileWillDeleteEvent { - - /** - * The files that are going to be deleted. - */ - readonly files: ReadonlyArray; - - /** - * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). - * - * *Note:* This function can only be called during event dispatch and not - * in an asynchronous manner: - * - * ```ts - * workspace.onWillCreateFiles(event => { - * // async, will *throw* an error - * setTimeout(() => event.waitUntil(promise)); - * - * // sync, OK - * event.waitUntil(promise); - * }) - * ``` - * - * @param thenable A thenable that delays saving. - */ - waitUntil(thenable: Thenable): void; - - /** - * Allows to pause the event until the provided thenable resolves. - * - * *Note:* This function can only be called during event dispatch. - * - * @param thenable A thenable that delays saving. - */ - waitUntil(thenable: Thenable): void; - } - - /** - * An event that is fired after files are deleted. - */ - export interface FileDeleteEvent { - - /** - * The files that got deleted. - */ - readonly files: ReadonlyArray; - } - - /** - * An event that is fired when files are going to be renamed. - * - * To make modifications to the workspace before the files are renamed, - * call the [`waitUntil](#FileWillCreateEvent.waitUntil)-function with a - * thenable that resolves to a [workspace edit](#WorkspaceEdit). - */ - export interface FileWillRenameEvent { - - /** - * The files that are going to be renamed. - */ - readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>; - - /** - * Allows to pause the event and to apply a [workspace edit](#WorkspaceEdit). - * - * *Note:* This function can only be called during event dispatch and not - * in an asynchronous manner: - * - * ```ts - * workspace.onWillCreateFiles(event => { - * // async, will *throw* an error - * setTimeout(() => event.waitUntil(promise)); - * - * // sync, OK - * event.waitUntil(promise); - * }) - * ``` - * - * @param thenable A thenable that delays saving. - */ - waitUntil(thenable: Thenable): void; - - /** - * Allows to pause the event until the provided thenable resolves. - * - * *Note:* This function can only be called during event dispatch. - * - * @param thenable A thenable that delays saving. - */ - waitUntil(thenable: Thenable): void; - } - - /** - * An event that is fired after files are renamed. - */ - export interface FileRenameEvent { - - /** - * The files that got renamed. - */ - readonly files: ReadonlyArray<{ oldUri: Uri, newUri: Uri }>; - } - - export namespace workspace { - - /** - * An event that is emitted when files are being created. - * - * *Note 1:* This event is triggered by user gestures, like creating a file from the - * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api. This event is *not* fired when - * files change on disk, e.g triggered by another application, or when using the - * [`workspace.fs`](#FileSystem)-api. - * - * *Note 2:* When this event is fired, edits to files thare are being created cannot be applied. - */ - export const onWillCreateFiles: Event; - - /** - * An event that is emitted when files have been created. - * - * *Note:* This event is triggered by user gestures, like creating a file from the - * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when - * files change on disk, e.g triggered by another application, or when using the - * [`workspace.fs`](#FileSystem)-api. - */ - export const onDidCreateFiles: Event; - - /** - * An event that is emitted when files are being deleted. - * - * *Note 1:* This event is triggered by user gestures, like deleting a file from the - * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when - * files change on disk, e.g triggered by another application, or when using the - * [`workspace.fs`](#FileSystem)-api. - * - * *Note 2:* When deleting a folder with children only one event is fired. - */ - export const onWillDeleteFiles: Event; - - /** - * An event that is emitted when files have been deleted. - * - * *Note 1:* This event is triggered by user gestures, like deleting a file from the - * explorer, or from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when - * files change on disk, e.g triggered by another application, or when using the - * [`workspace.fs`](#FileSystem)-api. - * - * *Note 2:* When deleting a folder with children only one event is fired. - */ - export const onDidDeleteFiles: Event; - - /** - * An event that is emitted when files are being renamed. - * - * *Note 1:* This event is triggered by user gestures, like renaming a file from the - * explorer, and from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when - * files change on disk, e.g triggered by another application, or when using the - * [`workspace.fs`](#FileSystem)-api. - * - * *Note 2:* When renaming a folder with children only one event is fired. - */ - export const onWillRenameFiles: Event; - - /** - * An event that is emitted when files have been renamed. - * - * *Note 1:* This event is triggered by user gestures, like renaming a file from the - * explorer, and from the [`workspace.applyEdit`](#workspace.applyEdit)-api, but this event is *not* fired when - * files change on disk, e.g triggered by another application, or when using the - * [`workspace.fs`](#FileSystem)-api. - * - * *Note 2:* When renaming a folder with children only one event is fired. - */ - export const onDidRenameFiles: Event; - } - //#endregion - //#region Alex - OnEnter enhancement export interface OnEnterRule { /** diff --git a/src/vs/workbench/api/browser/mainThreadDebugService.ts b/src/vs/workbench/api/browser/mainThreadDebugService.ts index bc0a47fb48..4495e90366 100644 --- a/src/vs/workbench/api/browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/browser/mainThreadDebugService.ts @@ -141,7 +141,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb } else if (dto.type === 'function') { this.debugService.addFunctionBreakpoint(dto.functionName, dto.id); } else if (dto.type === 'data') { - this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist); + this.debugService.addDataBreakpoint(dto.label, dto.dataId, dto.canPersist, dto.accessTypes); } } return Promise.resolve(); @@ -336,7 +336,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb condition: dbp.condition, hitCondition: dbp.hitCondition, logMessage: dbp.logMessage, - label: dbp.label, + label: dbp.description, canPersist: dbp.canPersist }; } else { diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index a4f784ca55..b93f33c3b7 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -271,18 +271,7 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma webviewInput.webview.options = options; webviewInput.webview.extension = extension; - const model = await this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType); - - model.onUndo(edits => { this._proxy.$undoEdits(handle, edits.map(x => x.data)); }); - model.onApplyEdit(edits => { - const editsToApply = edits.filter(x => x.source !== webviewInput).map(x => x.data); - if (editsToApply.length) { - this._proxy.$applyEdits(handle, editsToApply); - } - }); - model.onWillSave(e => { e.waitUntil(this._proxy.$onSave(handle)); }); - model.onWillSaveAs(e => { e.waitUntil(this._proxy.$onSaveAs(handle, e.resource.toJSON(), e.targetResource.toJSON())); }); - + const model = await this.getModel(webviewInput); webviewInput.onDisposeWebview(() => { this._customEditorService.models.disposeModel(model); }); @@ -315,6 +304,31 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma this._editorProviders.delete(viewType); } + public async $registerCapabilities(handle: extHostProtocol.WebviewPanelHandle, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]): Promise { + const webviewInput = this.getWebviewInput(handle); + const model = await this.getModel(webviewInput); + + const capabilitiesSet = new Set(capabilities); + if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.Editable)) { + model.onUndo(edits => { this._proxy.$undoEdits(handle, edits.map(x => x.data)); }); + + model.onApplyEdit(edits => { + const editsToApply = edits.filter(x => x.source !== webviewInput).map(x => x.data); + if (editsToApply.length) { + this._proxy.$applyEdits(handle, editsToApply); + } + }); + + model.onWillSave(e => { e.waitUntil(this._proxy.$onSave(handle)); }); + + model.onWillSaveAs(e => { e.waitUntil(this._proxy.$onSaveAs(handle, e.resource.toJSON(), e.targetResource.toJSON())); }); + } + } + + private getModel(webviewInput: WebviewInput) { + return this._customEditorService.models.loadOrCreate(webviewInput.getResource(), webviewInput.viewType); + } + public $onEdit(handle: extHostProtocol.WebviewPanelHandle, editData: any): void { const webview = this.getWebviewInput(handle); if (!(webview instanceof CustomFileEditorInput)) { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 8cc5597c08..30c266c720 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -705,27 +705,21 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostLabelService.$registerResourceLabelFormatter(formatter); }, onDidCreateFiles: (listener, thisArg, disposables) => { - checkProposedApiEnabled(extension); return extHostFileSystemEvent.onDidCreateFile(listener, thisArg, disposables); }, onDidDeleteFiles: (listener, thisArg, disposables) => { - checkProposedApiEnabled(extension); return extHostFileSystemEvent.onDidDeleteFile(listener, thisArg, disposables); }, onDidRenameFiles: (listener, thisArg, disposables) => { - checkProposedApiEnabled(extension); return extHostFileSystemEvent.onDidRenameFile(listener, thisArg, disposables); }, onWillCreateFiles: (listener: (e: vscode.FileWillCreateEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { - checkProposedApiEnabled(extension); return extHostFileSystemEvent.getOnWillCreateFileEvent(extension)(listener, thisArg, disposables); }, onWillDeleteFiles: (listener: (e: vscode.FileWillDeleteEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { - checkProposedApiEnabled(extension); return extHostFileSystemEvent.getOnWillDeleteFileEvent(extension)(listener, thisArg, disposables); }, onWillRenameFiles: (listener: (e: vscode.FileWillRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => { - checkProposedApiEnabled(extension); return extHostFileSystemEvent.getOnWillRenameFileEvent(extension)(listener, thisArg, disposables); } }; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 620fb2fc68..40ae774638 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -560,6 +560,10 @@ export interface WebviewExtensionDescription { readonly location: UriComponents; } +export enum WebviewEditorCapabilities { + Editable, +} + export interface MainThreadWebviewsShape extends IDisposable { $createWebviewPanel(extension: WebviewExtensionDescription, handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: modes.IWebviewPanelOptions & modes.IWebviewOptions): void; $disposeWebview(handle: WebviewPanelHandle): void; @@ -577,6 +581,7 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; $unregisterEditorProvider(viewType: string): void; + $registerCapabilities(handle: WebviewPanelHandle, capabilities: readonly WebviewEditorCapabilities[]): void; $onEdit(handle: WebviewPanelHandle, editJson: any): void; } @@ -1287,6 +1292,7 @@ export interface IDataBreakpointDto extends IBreakpointDto { dataId: string; canPersist: boolean; label: string; + accessTypes?: DebugProtocol.DataBreakpointAccessType[]; } export interface ISourceBreakpointDto extends IBreakpointDto { diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 8560d7f70c..865ba8e216 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1209,11 +1209,10 @@ class CallHierarchyAdapter { } releaseSession(sessionId: string): void { - this._cache.delete(sessionId.charAt(0)); + this._cache.delete(sessionId); } - private _cacheAndConvertItem(itemOrSessionId: string, item: vscode.CallHierarchyItem): extHostProtocol.ICallHierarchyItemDto { - const sessionId = itemOrSessionId.charAt(0); + private _cacheAndConvertItem(sessionId: string, item: vscode.CallHierarchyItem): extHostProtocol.ICallHierarchyItemDto { const map = this._cache.get(sessionId)!; const dto: extHostProtocol.ICallHierarchyItemDto = { _sessionId: sessionId, @@ -1231,7 +1230,7 @@ class CallHierarchyAdapter { private _itemFromCache(sessionId: string, itemId: string): vscode.CallHierarchyItem | undefined { const map = this._cache.get(sessionId); - return map && map.get(itemId); + return map?.get(itemId); } } diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index bf1f7917dc..1a12b48dc9 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -16,7 +16,7 @@ import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import * as vscode from 'vscode'; -import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; +import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewStateData, WebviewEditorCapabilities } from './extHost.protocol'; import { Disposable as VSCodeDisposable } from './extHostTypes'; type IconPath = URI | { light: URI, dark: URI }; @@ -257,12 +257,11 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa } async _onSave(): Promise { - await assertIsDefined(this._capabilities).editingCapability?.save(); + await assertIsDefined(this._capabilities?.editingCapability)?.save(); } - async _onSaveAs(resource: vscode.Uri, targetResource: vscode.Uri): Promise { - await assertIsDefined(this._capabilities).editingCapability?.saveAs(resource, targetResource); + await assertIsDefined(this._capabilities?.editingCapability)?.saveAs(resource, targetResource); } private assertNotDisposed() { @@ -450,6 +449,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { this._webviewPanels.set(handle, revivedPanel); const capabilities = await provider.resolveWebviewEditor({ resource: URI.revive(input.resource) }, revivedPanel); revivedPanel._setCapabilities(capabilities); + this.registerCapabilites(handle, capabilities); // TODO: the first set of edits should likely be passed when resolving if (input.edits.length) { @@ -480,6 +480,14 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined { return this._webviewPanels.get(handle); } + + private registerCapabilites(handle: WebviewPanelHandle, capabilities: vscode.WebviewEditorCapabilities) { + const declaredCapabilites: WebviewEditorCapabilities[] = []; + if (capabilities.editingCapability) { + declaredCapabilites.push(WebviewEditorCapabilities.Editable); + } + this._proxy.$registerCapabilities(handle, declaredCapabilites); + } } function convertWebviewOptions( diff --git a/src/vs/workbench/api/common/extHostWorkspace.ts b/src/vs/workbench/api/common/extHostWorkspace.ts index cc5d6519d7..9ea4c749b4 100644 --- a/src/vs/workbench/api/common/extHostWorkspace.ts +++ b/src/vs/workbench/api/common/extHostWorkspace.ts @@ -339,6 +339,11 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac if (folders.length === 0) { return undefined; } + + if (folders.length > 1) { + return undefined; + } + // #54483 @Joh Why are we still using fsPath? return folders[0].uri.fsPath; } diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 724cac1d07..250533cc42 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -180,6 +180,8 @@ body.web { .monaco-workbench select { -webkit-appearance: none; -moz-appearance: none; + /* Hides inner border from FF */ + border: 1px solid; } .monaco-workbench .select-container { diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css index 3c2705d257..4f63dfb9d7 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -7,21 +7,6 @@ width: 48px; } -.monaco-workbench.windows.chromium .part.activitybar, -.monaco-workbench.linux.chromium .part.activitybar { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); - overflow: visible; /* when a new layer is created, we need to set overflow visible to avoid clipping the menubar */ -} - .monaco-workbench .activitybar > .content { height: 100%; display: flex; diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 0e4130d1e1..12923804ea 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -854,6 +854,7 @@ class ShowCurrentMarkerInStatusbarContribution extends Disposable { update(editor: ICodeEditor | undefined): void { this.editor = editor; + this.updateMarkers(); this.updateStatus(); } diff --git a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css index ce023f628a..59677ad2f2 100644 --- a/src/vs/workbench/browser/parts/editor/media/editorgroupview.css +++ b/src/vs/workbench/browser/parts/editor/media/editorgroupview.css @@ -48,20 +48,6 @@ overflow: hidden; } -.monaco-workbench.windows.chromium .part.editor > .content .editor-group-container > .title, -.monaco-workbench.linux.chromium .part.editor > .content .editor-group-container > .title { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); -} - .monaco-workbench .part.editor > .content .editor-group-container > .title:not(.tabs) { display: flex; /* when tabs are not shown, use flex layout */ flex-wrap: nowrap; diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index fab8443ce6..33c3a6621c 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -12,20 +12,6 @@ z-index: initial; } -.monaco-workbench.windows.chromium .part.panel, -.monaco-workbench.linux.chromium .part.panel { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); -} - .monaco-workbench .part.panel .title { height: 35px; display: flex; @@ -114,7 +100,7 @@ text-align: center; display: inline-block; box-sizing: border-box; -} + } /** Actions */ diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index 1ed0387de3..54506f8a48 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -3,21 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench.windows.chromium .part.sidebar, -.monaco-workbench.linux.chromium .part.sidebar { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); -} - -.monaco-workbench .part.sidebar > .content { +.monaco-workbench .sidebar > .content { overflow: hidden; } diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index d27bc7a9f8..5778d8e043 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -13,20 +13,6 @@ overflow: visible; } -.monaco-workbench.windows.chromium .part.statusbar, -.monaco-workbench.linux.chromium .part.statusbar { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); -} - .monaco-workbench .part.statusbar.status-border-top::after { content: ''; position: absolute; @@ -51,7 +37,6 @@ .monaco-workbench .part.statusbar > .left-items { flex-grow: 1; /* left items push right items to the far right end */ - overflow: hidden; /* Hide the overflowing entries */ } .monaco-workbench .part.statusbar > .items-container > .statusbar-item { diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index efc5f85d4d..e55ac587c0 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -19,22 +19,6 @@ display: flex; } -.monaco-workbench.windows .part.titlebar, -.monaco-workbench.linux .part.titlebar { - /* - * Explicitly put the part onto its own layer to help Chrome to - * render the content with LCD-anti-aliasing. By partioning the - * workbench into multiple layers, we can ensure that a bad - * behaving part is not making another part fallback to greyscale - * rendering. - * - * macOS: does not render LCD-anti-aliased. - */ - transform: translate3d(0px, 0px, 0px); - position: relative; - z-index: 1000; /* move the entire titlebar above the workbench, except modals/dialogs */ -} - .monaco-workbench .part.titlebar > .titlebar-drag-region { top: 0; left: 0; @@ -45,11 +29,6 @@ -webkit-app-region: drag; } -.monaco-workbench .part.titlebar > .menubar { - /* Move above drag region since negative z-index on that element causes AA issues */ - z-index: 1; -} - .monaco-workbench .part.titlebar > .window-title { flex: 0 1 auto; font-size: 12px; @@ -78,6 +57,11 @@ cursor: default; } +.monaco-workbench .part.titlebar > .menubar { + /* move menubar above drag region as negative z-index on drag region cause greyscale AA */ + z-index: 2000; +} + .monaco-workbench.linux .part.titlebar > .window-title { font-size: inherit; } @@ -100,7 +84,7 @@ width: 35px; height: 100%; position: relative; - z-index: 2; /* highest level of titlebar */ + z-index: 3000; background-image: url('code-icon.svg'); background-repeat: no-repeat; background-position: center center; @@ -118,12 +102,11 @@ flex-shrink: 0; text-align: center; position: relative; - z-index: 2; /* highest level of titlebar */ + z-index: 3000; -webkit-app-region: no-drag; height: 100%; width: 138px; margin-left: auto; - transform: translate3d(0px, 0px, 0px); } .monaco-workbench.fullscreen .part.titlebar > .window-controls-container { diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index f477ee5796..2fc68eda07 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -101,8 +101,16 @@ export abstract class ViewContainerViewlet extends PaneViewlet implements IViews this.focus(); } - getContextMenuActions(): IAction[] { + getContextMenuActions(viewDescriptor?: IViewDescriptor): IAction[] { const result: IAction[] = []; + if (viewDescriptor) { + result.push({ + id: `${viewDescriptor.id}.removeView`, + label: localize('hideView', "Hide"), + enabled: viewDescriptor.canToggleVisibility, + run: () => this.toggleViewVisibility(viewDescriptor.id) + }); + } const viewToggleActions = this.viewsModel.viewDescriptors.map(viewDescriptor => ({ id: `${viewDescriptor.id}.toggleVisibility`, label: viewDescriptor.name, @@ -111,13 +119,17 @@ export abstract class ViewContainerViewlet extends PaneViewlet implements IViews run: () => this.toggleViewVisibility(viewDescriptor.id) })); - result.push(...viewToggleActions); - const parentActions = this.getViewletContextMenuActions(); - if (viewToggleActions.length && parentActions.length) { + if (result.length && viewToggleActions.length) { result.push(new Separator()); } + result.push(...viewToggleActions); + const parentActions = this.getViewletContextMenuActions(); + if (result.length && parentActions.length) { + result.push(new Separator()); + } result.push(...parentActions); + return result; } @@ -249,17 +261,7 @@ export abstract class ViewContainerViewlet extends PaneViewlet implements IViews event.stopPropagation(); event.preventDefault(); - const actions: IAction[] = []; - actions.push({ - id: `${viewDescriptor.id}.removeView`, - label: localize('hideView', "Hide"), - enabled: viewDescriptor.canToggleVisibility, - run: () => this.toggleViewVisibility(viewDescriptor.id) - }); - const otherActions = this.getContextMenuActions(); - if (otherActions.length) { - actions.push(...[new Separator(), ...otherActions]); - } + const actions: IAction[] = this.getContextMenuActions(viewDescriptor); let anchor: { x: number, y: number } = { x: event.posx, y: event.posy }; this.contextMenuService.showContextMenu({ @@ -377,6 +379,9 @@ export abstract class FilterViewContainerViewlet extends ViewContainerViewlet { protected abstract getFilterOn(viewDescriptor: IViewDescriptor): string | undefined; private onFilterChanged(newFilterValue: string) { + if (this.allViews.size === 0) { + this.updateAllViews(this.viewsModel.viewDescriptors); + } this.getViewsNotForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, false)); this.getViewsForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, true)); } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index 392c14b303..802c991cf2 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -24,7 +24,7 @@ import { Range } from 'vs/editor/common/core/range'; import { IPosition } from 'vs/editor/common/core/position'; import { MenuId } from 'vs/platform/actions/common/actions'; -const _ctxHasCompletionItemProvider = new RawContextKey('editorHasCallHierarchyProvider', false); +const _ctxHasCallHierarchyProvider = new RawContextKey('editorHasCallHierarchyProvider', false); const _ctxCallHierarchyVisible = new RawContextKey('callHierarchyVisible', false); class CallHierarchyController implements IEditorContribution { @@ -52,7 +52,7 @@ class CallHierarchyController implements IEditorContribution { @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { this._ctxIsVisible = _ctxCallHierarchyVisible.bindTo(this._contextKeyService); - this._ctxHasProvider = _ctxHasCompletionItemProvider.bindTo(this._contextKeyService); + this._ctxHasProvider = _ctxHasCallHierarchyProvider.bindTo(this._contextKeyService); this._dispoables.add(Event.any(_editor.onDidChangeModel, _editor.onDidChangeModelLanguage, CallHierarchyProviderRegistry.onDidChange)(() => { this._ctxHasProvider.set(_editor.hasModel() && CallHierarchyProviderRegistry.has(_editor.getModel())); })); @@ -172,8 +172,7 @@ registerEditorAction(class extends EditorAction { primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H }, precondition: ContextKeyExpr.and( - _ctxCallHierarchyVisible.negate(), - _ctxHasCompletionItemProvider, + _ctxHasCallHierarchyProvider, PeekContext.notInPeekEditor ) }); diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index fbc8cc33fa..203f11eeae 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -49,10 +49,10 @@ class ChangeHierarchyDirectionAction extends Action { }); const update = () => { if (getDirection() === CallHierarchyDirection.CallsFrom) { - this.label = localize('toggle.from', "Showing Calls"); + this.label = localize('toggle.from', "Show Incoming Calls"); this.class = 'calls-from'; } else { - this.label = localize('toggle.to', "Showing Callers"); + this.label = localize('toggle.to', "Showing Outgoing Calls"); this.class = 'calls-to'; } }; @@ -325,7 +325,11 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { // set decorations for caller ranges (if in the same file) let decorations: IModelDeltaDecoration[] = []; let fullRange: IRange | undefined; - for (const loc of element.locations) { + let locations = element.locations; + if (!locations) { + locations = [{ uri: element.item.uri, range: element.item.selectionRange }]; + } + for (const loc of locations) { if (loc.uri.toString() === previewUri.toString()) { decorations.push({ range: loc.range, options }); fullRange = !fullRange ? loc.range : Range.plusRange(loc.range, fullRange); @@ -424,7 +428,7 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { } protected _doLayoutBody(height: number, width: number): void { - if (this._dim.height !== height || this._dim.width === width) { + if (this._dim.height !== height || this._dim.width !== width) { super._doLayoutBody(height, width); this._dim = { height, width }; this._layoutInfo.height = this._viewZone ? this._viewZone.heightInLines : this._layoutInfo.height; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts index e64b2453f4..7c53071e32 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyTree.ts @@ -17,7 +17,7 @@ import { Range } from 'vs/editor/common/core/range'; export class Call { constructor( readonly item: CallHierarchyItem, - readonly locations: Location[], + readonly locations: Location[] | undefined, readonly model: CallHierarchyModel, readonly parent: Call | undefined ) { } @@ -43,7 +43,7 @@ export class DataSource implements IAsyncDataSource { async getChildren(element: CallHierarchyModel | Call): Promise { if (element instanceof CallHierarchyModel) { - return [new Call(element.root, [], element, undefined)]; + return [new Call(element.root, undefined, element, undefined)]; } const { model, item } = element; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from-dark.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from-dark.svg new file mode 100644 index 0000000000..66406bfc5d --- /dev/null +++ b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from.svg new file mode 100644 index 0000000000..b65e2d14a4 --- /dev/null +++ b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-from.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to-dark.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to-dark.svg new file mode 100644 index 0000000000..ff488f1ed4 --- /dev/null +++ b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to.svg new file mode 100644 index 0000000000..159e5b92ea --- /dev/null +++ b/src/vs/workbench/contrib/callHierarchy/browser/media/action-call-to.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css b/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css index 0f1345c235..2e540bc016 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css +++ b/src/vs/workbench/contrib/callHierarchy/browser/media/callHierarchy.css @@ -21,19 +21,27 @@ } .monaco-workbench .action-label.calls-to { - background-image: url(files_CallTo_CallTo_16x.svg); + background-image: url(action-call-to.svg); background-size: 14px 14px; background-repeat: no-repeat; background-position: left center; } +.vs-dark .monaco-workbench .action-label.calls-to { + background-image: url(action-call-to-dark.svg); +} + .monaco-workbench .action-label.calls-from { - background-image: url(files_CallFrom_CallFrom_16x.svg); + background-image: url(action-call-from.svg); background-size: 14px 14px; background-repeat: no-repeat; background-position: left center; } +.vs-dark .monaco-workbench .action-label.calls-from{ + background-image: url(action-call-from-dark.svg); +} + .monaco-workbench .call-hierarchy .editor, .monaco-workbench .call-hierarchy .tree { height: 100%; diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/files_CallFrom_CallFrom_16x.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/files_CallFrom_CallFrom_16x.svg deleted file mode 100644 index 667f3e7655..0000000000 --- a/src/vs/workbench/contrib/callHierarchy/browser/media/files_CallFrom_CallFrom_16x.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/callHierarchy/browser/media/files_CallTo_CallTo_16x.svg b/src/vs/workbench/contrib/callHierarchy/browser/media/files_CallTo_CallTo_16x.svg deleted file mode 100644 index 26c436e77c..0000000000 --- a/src/vs/workbench/contrib/callHierarchy/browser/media/files_CallTo_CallTo_16x.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/vs/workbench/contrib/customEditor/browser/commands.ts b/src/vs/workbench/contrib/customEditor/browser/commands.ts index b6333d20cf..8cbf25001b 100644 --- a/src/vs/workbench/contrib/customEditor/browser/commands.ts +++ b/src/vs/workbench/contrib/customEditor/browser/commands.ts @@ -20,6 +20,7 @@ import { CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CONTEXT_HAS_CUSTOM_EDITORS, import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; const viewCategory = nls.localize('viewCategory', "View"); @@ -34,7 +35,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: EditorContextKeys.focus.toNegated(), handler: async (accessor: ServicesAccessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, accessor.get(IExplorerService)); const targetResource = firstOrDefault(resources); if (!targetResource) { return undefined; // {{SQL CARBON EDIT}} strict-null-check diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 336b873263..3cf5721aaa 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -17,6 +17,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, Verbosity } from 'vs/workbench/common/editor'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -117,26 +118,19 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return false; } - // Preserve view state by opening the editor first. In addition - // this allows the user to review the contents of the editor. - // let viewState: IEditorViewState | undefined = undefined; - // const editor = await this.editorService.openEditor(this, undefined, group); - // if (isTextEditor(editor)) { - // viewState = editor.getViewState(); - // } - let dialogPath = this._editorResource; - // if (this._editorResource.scheme === Schemas.untitled) { - // dialogPath = this.suggestFileName(resource); - // } - const target = await this.promptForPath(this._editorResource, dialogPath, options?.availableFileSystems); if (!target) { return false; // save cancelled } - await this._model.saveAs(this._editorResource, target, options); + if (!await this._model.saveAs(this._editorResource, target, options)) { + return false; + } + const replacement = this.handleMove(groupId, target) || this.instantiationService.createInstance(FileEditorInput, target, undefined, undefined); + + await this.editorService.replaceEditors([{ editor: this, replacement, options: { pinned: true } }], groupId); return true; } @@ -156,15 +150,24 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { // Help user to find a name for the file by opening it first await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - return this.fileDialogService.pickFileToSave({});//this.getSaveDialogOptions(defaultUri, availableFileSystems)); + return this.fileDialogService.pickFileToSave({ + availableFileSystems, + defaultUri + }); } public handleMove(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { - const webview = assertIsDefined(this.takeOwnershipOfWebview()); - return this.instantiationService.createInstance(CustomFileEditorInput, - uri, - this.viewType, - generateUuid(), - new Lazy(() => webview)); + const editorInfo = this.customEditorService.getCustomEditor(this.viewType); + if (editorInfo?.matches(uri)) { + const webview = assertIsDefined(this.takeOwnershipOfWebview()); + const newInput = this.instantiationService.createInstance(CustomFileEditorInput, + uri, + this.viewType, + generateUuid(), + new Lazy(() => webview)); + newInput.updateGroup(groupId); + return newInput; + } + return undefined; } } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index a7d38c795d..3bb8106a46 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { coalesce, distinct, find, mergeSort } from 'vs/base/common/arrays'; -import * as glob from 'vs/base/common/glob'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { basename, isEqual } from 'vs/base/common/resources'; @@ -32,14 +31,14 @@ import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/wo import { CustomFileEditorInput } from './customEditorInput'; const defaultEditorId = 'default'; -const defaultEditorInfo: CustomEditorInfo = { +const defaultEditorInfo = new CustomEditorInfo({ id: defaultEditorId, displayName: nls.localize('promptOpenWith.defaultEditor', "VS Code's standard text editor"), selector: [ { filenamePattern: '*' } ], priority: CustomEditorPriority.default, -}; +}); export class CustomEditorInfoStore { private readonly contributedEditors = new Map(); @@ -64,7 +63,7 @@ export class CustomEditorInfoStore { public getContributedEditors(resource: URI): readonly CustomEditorInfo[] { return Array.from(this.contributedEditors.values()).filter(customEditor => - customEditor.selector.some(selector => matches(selector, resource))); + customEditor.matches(resource)); } } @@ -97,12 +96,12 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ for (const extension of extensions) { for (const webviewEditorContribution of extension.value) { - this._editorInfoStore.add({ + this._editorInfoStore.add(new CustomEditorInfo({ id: webviewEditorContribution.viewType, displayName: webviewEditorContribution.displayName, selector: webviewEditorContribution.selector || [], priority: webviewEditorContribution.priority || CustomEditorPriority.default, - }); + })); } } this.updateContexts(); @@ -127,6 +126,10 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ return { resource, viewType: activeInput.viewType }; } + public getCustomEditor(viewType: string): CustomEditorInfo | undefined { + return this._editorInfoStore.get(viewType); + } + public getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[] { return this._editorInfoStore.getContributedEditors(resource); } @@ -134,7 +137,7 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ public getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[] { const rawAssociations = this.configurationService.getValue(customEditorsAssociationsKey) || []; return coalesce(rawAssociations - .filter(association => matches(association, resource)) + .filter(association => CustomEditorInfo.selectorMatches(association, resource)) .map(association => this._editorInfoStore.get(association.viewType))); } @@ -332,6 +335,9 @@ export class CustomEditorContribution implements IWorkbenchContribution { return { override: (async () => { const standardEditor = await this.editorService.openEditor(editor, { ...options, ignoreOverrides: true }, group); + // Give a moment to make sure the editor is showing. + // Otherwise the focus shift can cause the prompt to be dismissed right away. + await new Promise(resolve => setTimeout(resolve, 20)); const selectedEditor = await this.customEditorService.promptOpenWith(resource, options, group); if (selectedEditor && selectedEditor.input) { await group.replaceEditors([{ @@ -405,16 +411,6 @@ function priorityToRank(priority: CustomEditorPriority): number { } } -function matches(selector: CustomEditorSelector, resource: URI): boolean { - if (selector.filenamePattern) { - if (glob.match(selector.filenamePattern.toLowerCase(), basename(resource).toLowerCase())) { - return true; - } - } - - return false; -} - registerThemingParticipant((theme, collector) => { const shadow = theme.getColor(colorRegistry.scrollbarShadow); if (shadow) { diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 39a9dfcd62..deb8d01589 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -4,11 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; +import * as glob from 'vs/base/common/glob'; +import { basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { EditorInput, IEditor, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; +import { EditorInput, IEditor, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -29,6 +31,7 @@ export interface ICustomEditorService { readonly activeCustomEditor: ICustomEditor | undefined; + getCustomEditor(viewType: string): CustomEditorInfo | undefined; getContributedCustomEditors(resource: URI): readonly CustomEditorInfo[]; getUserConfiguredCustomEditors(resource: URI): readonly CustomEditorInfo[]; @@ -87,9 +90,35 @@ export interface CustomEditorSelector { readonly filenamePattern?: string; } -export interface CustomEditorInfo { - readonly id: string; - readonly displayName: string; - readonly priority: CustomEditorPriority; - readonly selector: readonly CustomEditorSelector[]; +export class CustomEditorInfo { + + public readonly id: string; + public readonly displayName: string; + public readonly priority: CustomEditorPriority; + public readonly selector: readonly CustomEditorSelector[]; + + constructor(descriptor: { + readonly id: string; + readonly displayName: string; + readonly priority: CustomEditorPriority; + readonly selector: readonly CustomEditorSelector[]; + }) { + this.id = descriptor.id; + this.displayName = descriptor.displayName; + this.priority = descriptor.priority; + this.selector = descriptor.selector; + } + + matches(resource: URI): boolean { + return this.selector.some(selector => CustomEditorInfo.selectorMatches(selector, resource)); + } + + static selectorMatches(selector: CustomEditorSelector, resource: URI): boolean { + if (selector.filenamePattern) { + if (glob.match(selector.filenamePattern.toLowerCase(), basename(resource).toLowerCase())) { + return true; + } + } + return false; + } } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 8deb08fcaf..6269b164e2 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -500,7 +500,7 @@ class DataBreakpointsRenderer implements IListRenderer(ViewletExtensions.Viewlets).registerViewlet(ViewletDescriptor.create( DebugViewlet, VIEWLET_ID, - nls.localize('debugAndRun', "Debug And Run"), + nls.localize('debugAndRun', "Debug and Run"), 'codicon-debug', 13 // {{SQL CARBON EDIT}} )); diff --git a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts index 0126ee998d..c84401a109 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCallStackContribution.ts @@ -186,9 +186,9 @@ registerThemingParticipant((theme, collector) => { .monaco-workbench .codicon-debug-breakpoint-function, .monaco-workbench .codicon-debug-breakpoint-data, .monaco-workbench .codicon-debug-breakpoint-unsupported, - .monaco-workbench .codicon-debug-hint:not(*[class*='codicon-debug-breakpoint']) , - .monaco-workbench .codicon-debug-breakpoint-stackframe-dot, - .monaco-workbench .codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after { + .monaco-workbench .codicon-debug-hint:not([class*='codicon-debug-breakpoint']), + .monaco-workbench .codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after, + .monaco-workbench .codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe::after { color: ${debugIconBreakpointColor} !important; } `); @@ -212,17 +212,16 @@ registerThemingParticipant((theme, collector) => { `); } - const debugIconBreakpointStackframeColor = theme.getColor(debugIconBreakpointStackframeForeground); - if (debugIconBreakpointStackframeColor) { + const debugIconBreakpointCurrentStackframeForegroundColor = theme.getColor(debugIconBreakpointCurrentStackframeForeground); + if (debugIconBreakpointCurrentStackframeForegroundColor) { collector.addRule(` - .monaco-workbench .codicon-debug-breakpoint-stackframe, - .monaco-workbench .codicon-debug-breakpoint-stackframe-dot::after { - color: ${debugIconBreakpointStackframeColor} !important; + .monaco-workbench .codicon-debug-breakpoint-stackframe { + color: ${debugIconBreakpointCurrentStackframeForegroundColor} !important; } `); } - const debugIconBreakpointStackframeFocusedColor = theme.getColor(debugIconBreakpointStackframeFocusedForeground); + const debugIconBreakpointStackframeFocusedColor = theme.getColor(debugIconBreakpointStackframeForeground); if (debugIconBreakpointStackframeFocusedColor) { collector.addRule(` .monaco-workbench .codicon-debug-breakpoint-stackframe-focused { @@ -239,5 +238,5 @@ const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightB const debugIconBreakpointForeground = registerColor('debugIcon.breakpointForeground', { dark: '#E51400', light: '#E51400', hc: '#E51400' }, localize('debugIcon.breakpointForeground', 'Icon color for breakpoints.')); const debugIconBreakpointDisabledForeground = registerColor('debugIcon.breakpointDisabledForeground', { dark: '#848484', light: '#848484', hc: '#848484' }, localize('debugIcon.breakpointDisabledForeground', 'Icon color for disabled breakpoints.')); const debugIconBreakpointUnverifiedForeground = registerColor('debugIcon.breakpointUnverifiedForeground', { dark: '#848484', light: '#848484', hc: '#848484' }, localize('debugIcon.breakpointUnverifiedForeground', 'Icon color for unverified breakpoints.')); -const debugIconBreakpointStackframeForeground = registerColor('debugIcon.breakpointStackframeForeground', { dark: '#FFCC00', light: '#FFCC00', hc: '#FFCC00' }, localize('debugIcon.breakpointStackframeForeground', 'Icon color for breakpoints.')); -const debugIconBreakpointStackframeFocusedForeground = registerColor('debugIcon.breakpointStackframeFocusedForeground', { dark: '#89D185', light: '#89D185', hc: '#89D185' }, localize('debugIcon.breakpointStackframeFocusedForeground', 'Icon color for breakpoints.')); +const debugIconBreakpointCurrentStackframeForeground = registerColor('debugIcon.breakpointCurrentStackframeForeground', { dark: '#FFCC00', light: '#FFCC00', hc: '#FFCC00' }, localize('debugIcon.breakpointCurrentStackframeForeground', 'Icon color for the current breakpoint stack frame.')); +const debugIconBreakpointStackframeForeground = registerColor('debugIcon.breakpointStackframeForeground', { dark: '#89D185', light: '#89D185', hc: '#89D185' }, localize('debugIcon.breakpointStackframeForeground', 'Icon color for all breakpoint stack frames.')); diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index bc1c86b7e7..ec3aee686b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -18,7 +18,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; -import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory } from 'vs/workbench/contrib/debug/common/debug'; @@ -80,7 +80,7 @@ export class ConfigurationManager implements IConfigurationManager { const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE); const previousSelectedLaunch = this.launches.filter(l => l.uri.toString() === previousSelectedRoot).pop(); this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService); - if (previousSelectedLaunch) { + if (previousSelectedLaunch && previousSelectedLaunch.getConfigurationNames().length) { this.selectConfiguration(previousSelectedLaunch, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE)); } else if (this.launches.length > 0) { const rootUri = historyService.getLastActiveWorkspaceRoot(); @@ -284,7 +284,7 @@ export class ConfigurationManager implements IConfigurationManager { }); }); - this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => { + this.toDispose.push(Event.any(this.contextService.onDidChangeWorkspaceFolders, this.contextService.onDidChangeWorkbenchState)(() => { this.initLaunches(); const toSelect = this.selectedLaunch || (this.launches.length > 0 ? this.launches[0] : undefined); this.selectConfiguration(toSelect); @@ -496,10 +496,13 @@ abstract class AbstractLaunch { getConfigurationNames(includeCompounds = true): string[] { const config = this.getConfig(); - if (!config || !config.configurations || !Array.isArray(config.configurations)) { + if (!config || (!Array.isArray(config.configurations) && !Array.isArray(config.compounds))) { return []; } else { - const names = config.configurations.filter(cfg => cfg && typeof cfg.name === 'string').map(cfg => cfg.name); + const names: string[] = []; + if (config.configurations) { + names.push(...config.configurations.filter(cfg => cfg && typeof cfg.name === 'string').map(cfg => cfg.name)); + } if (includeCompounds && config.compounds) { if (config.compounds) { names.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index e94a2550ff..78e10a6548 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -41,7 +41,7 @@ import { deepClone, equals } from 'vs/base/common/objects'; import { DebugSession } from 'vs/workbench/contrib/debug/browser/debugSession'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel, IDebugSessionOptions, CONTEXT_DEBUG_UX } from 'vs/workbench/contrib/debug/common/debug'; -import { isExtensionHostDebugging } from 'vs/workbench/contrib/debug/common/debugUtils'; +import { getExtensionHostDebugSession } from 'vs/workbench/contrib/debug/common/debugUtils'; import { isErrorWithActions, createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; @@ -232,7 +232,8 @@ export class DebugService implements IDebugService { if (this.previousState !== state) { this.debugState.set(getStateLabel(state)); this.inDebugMode.set(state !== State.Inactive); - this.debugUx.set(!!(state !== State.Inactive || this.configurationManager.selectedConfiguration.name) ? 'default' : 'simple'); + // Only show the simple ux if debug is not yet started and if no launch.json exists + this.debugUx.set(((state !== State.Inactive && state !== State.Initializing) || this.configurationManager.selectedConfiguration.name) ? 'default' : 'simple'); this.previousState = state; this._onDidChangeState.fire(state); } @@ -538,8 +539,9 @@ export class DebugService implements IDebugService { } // 'Run without debugging' mode VSCode must terminate the extension host. More details: #3905 - if (isExtensionHostDebugging(session.configuration) && session.state === State.Running && session.configuration.noDebug) { - this.extensionHostDebugService.close(session.getId()); + const extensionDebugSession = getExtensionHostDebugSession(session); + if (extensionDebugSession && extensionDebugSession.state === State.Running && extensionDebugSession.configuration.noDebug) { + this.extensionHostDebugService.close(extensionDebugSession.getId()); } this.telemetryDebugSessionStop(session, adapterExitEvent); @@ -589,10 +591,11 @@ export class DebugService implements IDebugService { return this.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask); }; - if (isExtensionHostDebugging(session.configuration)) { + const extensionDebugSession = getExtensionHostDebugSession(session); + if (extensionDebugSession) { const taskResult = await runTasks(); if (taskResult === TaskRunResult.Success) { - this.extensionHostDebugService.reload(session.getId()); + this.extensionHostDebugService.reload(extensionDebugSession.getId()); } return; @@ -984,8 +987,8 @@ export class DebugService implements IDebugService { this.storeBreakpoints(); } - async addDataBreakpoint(label: string, dataId: string, canPersist: boolean): Promise { - this.model.addDataBreakpoint(label, dataId, canPersist); + async addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined): Promise { + this.model.addDataBreakpoint(label, dataId, canPersist, accessTypes); await this.sendDataBreakpoints(); this.storeBreakpoints(); @@ -1097,7 +1100,7 @@ export class DebugService implements IDebugService { let result: DataBreakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_DATA_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((dbp: any) => { - return new DataBreakpoint(dbp.label, dbp.dataId, true, dbp.enabled, dbp.hitCondition, dbp.condition, dbp.logMessage); + return new DataBreakpoint(dbp.description, dbp.dataId, true, dbp.enabled, dbp.hitCondition, dbp.condition, dbp.logMessage, dbp.accessTypes); }); } catch (e) { } diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 9d89a18432..b19bd936e2 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -15,7 +15,7 @@ import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workben import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IProgressService } from 'vs/platform/progress/common/progress'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -119,22 +119,27 @@ export class DebugViewlet extends ViewContainerViewlet { if (CONTEXT_DEBUG_UX.getValue(this.contextKeyService) === 'simple') { return []; } - if (this.showInitialDebugActions) { - return [this.startAction, this.configureAction, this.toggleReplAction]; + if (!this.showInitialDebugActions) { + + if (!this.debugToolBarMenu) { + this.debugToolBarMenu = this.menuService.createMenu(MenuId.DebugToolBar, this.contextKeyService); + this._register(this.debugToolBarMenu); + } + + const { actions, disposable } = DebugToolBar.getActions(this.debugToolBarMenu, this.debugService, this.instantiationService); + if (this.disposeOnTitleUpdate) { + dispose(this.disposeOnTitleUpdate); + } + this.disposeOnTitleUpdate = disposable; + + return actions; } - if (!this.debugToolBarMenu) { - this.debugToolBarMenu = this.menuService.createMenu(MenuId.DebugToolBar, this.contextKeyService); - this._register(this.debugToolBarMenu); + if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { + return [this.toggleReplAction]; } - const { actions, disposable } = DebugToolBar.getActions(this.debugToolBarMenu, this.debugService, this.instantiationService); - if (this.disposeOnTitleUpdate) { - dispose(this.disposeOnTitleUpdate); - } - this.disposeOnTitleUpdate = disposable; - - return actions; + return [this.startAction, this.configureAction, this.toggleReplAction]; } get showInitialDebugActions(): boolean { diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index b009497a75..608b8f2696 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -7,7 +7,7 @@ cursor: pointer; } -.codicon-debug-hint:not(*[class*='codicon-debug-breakpoint']) { +.codicon-debug-hint:not([class*='codicon-debug-breakpoint']) { opacity: .4 !important; } @@ -16,26 +16,10 @@ align-items: center; } -/* overlapped icons */ -.inline-breakpoint-widget.codicon-debug-breakpoint-stackframe-dot::after { - position: absolute; - top: 0; - left: 0; - bottom: 0; - margin: auto; - display: table; -} - -.codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after { - position: absolute; -} - -.inline-breakpoint-widget.codicon-debug-breakpoint-stackframe-dot::after { - content: "\eb8b"; -} - -.codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after { +.codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe-focused::after, +.codicon-debug-breakpoint.codicon-debug-breakpoint-stackframe::after { content: "\eb8a"; + position: absolute; } .monaco-editor .inline-breakpoint-widget.line-start { diff --git a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css index 357e8ab1c1..660d52a085 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugToolBar.css @@ -5,7 +5,7 @@ .monaco-workbench .debug-toolbar { position: absolute; - z-index: 200; + z-index: 1000; height: 32px; display: flex; padding-left: 7px; diff --git a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index 4dd78b8c4d..dc27c4abd9 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -374,10 +374,6 @@ justify-content: center; } -.debug-viewlet .debug-breakpoints .breakpoint > .codicon-debug-breakpoint-stackframe-dot::before { - content: "\ea71"; -} - .debug-viewlet .debug-breakpoints .breakpoint > .file-path { opacity: 0.7; font-size: 0.9em; diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index 5aee190454..b7db048822 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -30,6 +30,9 @@ cursor: pointer; } +.monaco-workbench.mac .repl .repl-tree .monaco-tl-twistie { + padding-right: 0px; +} .repl .repl-tree .output.expression.value-and-source { display: flex; @@ -42,11 +45,11 @@ .repl .repl-tree .monaco-tl-contents .arrow { position:absolute; left: 2px; - opacity: 0.3; + opacity: 0.25; } .vs-dark .repl .repl-tree .monaco-tl-contents .arrow { - opacity: 0.45; + opacity: 0.4; } .repl .repl-tree .output.expression.value-and-source .source { diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 7204f2e38a..acd13bf6b1 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -610,7 +610,7 @@ class ReplEvaluationInputsRenderer implements ITreeRenderer { secondMessageElement.textContent = localize('specifyHowToRun', "To futher configure Debug and Run"); - const clickElement = $('span.click'); - clickElement.textContent = localize('configure', " create a launch.json file."); - clickElement.onclick = () => this.commandService.executeCommand(ConfigureAction.ID); + const clickElement = this.createClickElement(localize('configure', " create a launch.json file."), () => this.commandService.executeCommand(ConfigureAction.ID)); this.secondMessageContainer.appendChild(clickElement); }; const setSecondMessageWithFolder = () => { secondMessageElement.textContent = localize('noLaunchConfiguration', "To futher configure Debug and Run, "); - const clickElement = $('span.click'); - clickElement.textContent = localize('openFolder', " open a folder"); - clickElement.onclick = () => this.dialogService.pickFolderAndOpen({ forceNewWindow: false }); + const clickElement = this.createClickElement(localize('openFolder', " open a folder"), () => this.dialogService.pickFolderAndOpen({ forceNewWindow: false })); this.secondMessageContainer.appendChild(clickElement); const moreText = $('span.moreText'); @@ -106,10 +104,7 @@ export class StartView extends ViewletPane { } if (!enabled && emptyWorkbench) { - const clickElement = $('span.click'); - clickElement.textContent = localize('openFile', "Open a file"); - clickElement.onclick = () => this.dialogService.pickFileAndOpen({ forceNewWindow: false }); - + const clickElement = this.createClickElement(localize('openFile', "Open a file"), () => this.dialogService.pickFileAndOpen({ forceNewWindow: false })); this.firstMessageContainer.appendChild(clickElement); const firstMessageElement = $('span'); this.firstMessageContainer.appendChild(firstMessageElement); @@ -121,6 +116,21 @@ export class StartView extends ViewletPane { } } + private createClickElement(textContent: string, action: () => any): HTMLSpanElement { + const clickElement = $('span.click'); + clickElement.textContent = textContent; + clickElement.onclick = action; + clickElement.tabIndex = 0; + clickElement.onkeyup = (e) => { + const keyboardEvent = new StandardKeyboardEvent(e); + if (keyboardEvent.keyCode === KeyCode.Enter || (keyboardEvent.keyCode === KeyCode.Space)) { + action(); + } + }; + + return clickElement; + } + protected renderBody(container: HTMLElement): void { this.firstMessageContainer = $('.top-section'); container.appendChild(this.firstMessageContainer); diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 99589e4c6c..e44008bb4d 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -185,7 +185,7 @@ export class VariablesView extends ViewletPane { if (dataid) { actions.push(new Separator()); actions.push(new Action('debug.breakWhenValueChanges', nls.localize('breakWhenValueChanges', "Break When Value Changes"), undefined, true, () => { - return this.debugService.addDataBreakpoint(response.description, dataid, !!response.canPersist); + return this.debugService.addDataBreakpoint(response.description, dataid, !!response.canPersist, response.accessTypes); })); } } diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 8773032454..a5735393e9 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -215,7 +215,7 @@ export interface IDebugSession extends ITreeElement { sendBreakpoints(modelUri: uri, bpts: IBreakpoint[], sourceModified: boolean): Promise; sendFunctionBreakpoints(fbps: IFunctionBreakpoint[]): Promise; - dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean }>; + dataBreakpointInfo(name: string, variablesReference?: number): Promise<{ dataId: string | null, description: string, canPersist?: boolean, accessTypes?: DebugProtocol.DataBreakpointAccessType[] }>; sendDataBreakpoints(dbps: IDataBreakpoint[]): Promise; sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise; breakpointsLocations(uri: uri, lineNumber: number): Promise; @@ -377,7 +377,7 @@ export interface IExceptionBreakpoint extends IEnablement { } export interface IDataBreakpoint extends IBaseBreakpoint { - readonly label: string; + readonly description: string; readonly dataId: string; readonly canPersist: boolean; } @@ -795,7 +795,7 @@ export interface IDebugService { /** * Adds a new data breakpoint. */ - addDataBreakpoint(label: string, dataId: string, canPersist: boolean): Promise; + addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined): Promise; /** * Removes all data breakpoints. If id is passed only removes the data breakpoint with the passed id. diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index d1b6e39ffb..3c6f583726 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -748,13 +748,14 @@ export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreak export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { constructor( - public label: string, + public description: string, public dataId: string, public canPersist: boolean, enabled: boolean, hitCondition: string | undefined, condition: string | undefined, logMessage: string | undefined, + private accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined, id = generateUuid() ) { super(enabled, hitCondition, condition, logMessage, id); @@ -762,8 +763,9 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { toJSON(): any { const result = super.toJSON(); - result.label = this.label; - result.dataid = this.dataId; + result.description = this.description; + result.dataId = this.dataId; + result.accessTypes = this.accessTypes; return result; } @@ -777,7 +779,7 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { } toString(): string { - return this.label; + return this.description; } } @@ -1159,8 +1161,8 @@ export class DebugModel implements IDebugModel { this._onDidChangeBreakpoints.fire({ removed }); } - addDataBreakpoint(label: string, dataId: string, canPersist: boolean): void { - const newDataBreakpoint = new DataBreakpoint(label, dataId, canPersist, true, undefined, undefined, undefined); + addDataBreakpoint(label: string, dataId: string, canPersist: boolean, accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined): void { + const newDataBreakpoint = new DataBreakpoint(label, dataId, canPersist, true, undefined, undefined, undefined, accessTypes); this.dataBreakopints.push(newDataBreakpoint); this._onDidChangeBreakpoints.fire({ added: [newDataBreakpoint] }); } diff --git a/src/vs/workbench/contrib/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts index 2cd10f460e..29c87bf571 100644 --- a/src/vs/workbench/contrib/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { equalsIgnoreCase } from 'vs/base/common/strings'; -import { IConfig, IDebuggerContribution, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { IDebuggerContribution, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { URI as uri } from 'vs/base/common/uri'; import { isAbsolute } from 'vs/base/common/path'; import { deepClone } from 'vs/base/common/objects'; @@ -24,19 +24,28 @@ export function formatPII(value: string, excludePII: boolean, args: { [key: stri } export function isSessionAttach(session: IDebugSession): boolean { - return !session.parentSession && session.configuration.request === 'attach' && !isExtensionHostDebugging(session.configuration); + return !session.parentSession && session.configuration.request === 'attach' && !getExtensionHostDebugSession(session); } -export function isExtensionHostDebugging(config: IConfig) { - if (!config.type) { - return false; +/** + * Returns the session or any parent which is an extension host debug session. + * Returns undefined if there's none. + */ +export function getExtensionHostDebugSession(session: IDebugSession): IDebugSession | void { + let type = session.configuration.type; + if (!type) { + return; } - const type = config.type === 'vslsShare' - ? (config).adapterProxy.configuration.type - : config.type; + if (type === 'vslsShare') { + type = (session.configuration).adapterProxy.configuration.type; + } - return equalsIgnoreCase(type, 'extensionhost') || equalsIgnoreCase(type, 'pwa-extensionhost'); + if (equalsIgnoreCase(type, 'extensionhost') || equalsIgnoreCase(type, 'pwa-extensionhost')) { + return session; + } + + return session.parentSession ? getExtensionHostDebugSession(session.parentSession) : undefined; } // only a debugger contributions with a label, program, or runtime attribute is considered a "defining" or "main" debugger contribution diff --git a/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css b/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css index 18a6e43b20..4c13879379 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css @@ -22,6 +22,10 @@ font-weight: bold; } +.runtime-extensions-editor .extension .desc .msg .codicon { + vertical-align: middle; +} + .runtime-extensions-editor .extension .time { padding: 4px; text-align: right; diff --git a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts index d9bffbf186..57a1ce2a1a 100644 --- a/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution.ts @@ -24,6 +24,7 @@ import { distinct } from 'vs/base/common/arrays'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { optional } from 'vs/platform/instantiation/common/instantiation'; +import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; const OPEN_IN_TERMINAL_COMMAND_ID = 'openInTerminal'; @@ -37,7 +38,7 @@ CommandsRegistry.registerCommand({ const integratedTerminalService = accessor.get(IIntegratedTerminalService); const remoteAgentService = accessor.get(IRemoteAgentService); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, accessor.get(IExplorerService)); return fileService.resolveAll(resources.map(r => ({ resource: r }))).then(async stats => { const targets = distinct(stats.filter(data => data.success)); // Always use integrated terminal when using a remote @@ -162,4 +163,3 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { command: openConsoleCommand, when: ResourceContextKey.Scheme.isEqualTo(Schemas.vscodeRemote) }); - diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index e2825fad66..46456e5ddd 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -1001,7 +1001,12 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { const explorerService = accessor.get(IExplorerService); const stats = explorerService.getContext(true); + let canceled = false; stats.forEach(async s => { + if (canceled) { + return; + } + if (isWeb) { if (!s.isDirectory) { triggerDownload(asDomUri(s.resource), s.name); @@ -1020,6 +1025,9 @@ const downloadFileHandler = (accessor: ServicesAccessor) => { }); if (destination) { await textFileService.copy(s.resource, destination); + } else { + // User canceled a download. In case there were multiple files selected we should cancel the remainder of the prompts #86100 + canceled = true; } } }); diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 7e46934f1b..c9ed8e6d55 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -119,7 +119,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const editorService = accessor.get(IEditorService); const listService = accessor.get(IListService); const fileService = accessor.get(IFileService); - const resources = getMultiSelectedResources(resource, listService, editorService); + const explorerService = accessor.get(IExplorerService); + const resources = getMultiSelectedResources(resource, listService, editorService, explorerService); // Set side input if (resources.length) { @@ -201,7 +202,8 @@ CommandsRegistry.registerCommand({ id: COMPARE_SELECTED_COMMAND_ID, handler: (accessor, resource: URI | object) => { const editorService = accessor.get(IEditorService); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService); + const explorerService = accessor.get(IExplorerService); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService, explorerService); if (resources.length === 2) { return editorService.openEditor({ @@ -251,7 +253,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }, id: COPY_PATH_COMMAND_ID, handler: async (accessor, resource: URI | object) => { - const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IExplorerService)); await resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService)); } }); @@ -265,7 +267,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }, id: COPY_RELATIVE_PATH_COMMAND_ID, handler: async (accessor, resource: URI | object) => { - const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IExplorerService)); await resourcesToClipboard(resources, true, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService)); } }); @@ -488,7 +490,7 @@ CommandsRegistry.registerCommand({ const workspaceEditingService = accessor.get(IWorkspaceEditingService); const contextService = accessor.get(IWorkspaceContextService); const workspace = contextService.getWorkspace(); - const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)).filter(r => + const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IExplorerService)).filter(r => // Need to verify resources are workspaces since multi selection can trigger this command on some non workspace resources workspace.folders.some(f => isEqual(f.uri, r)) ); diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index cced7d2513..23200282c9 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { IListService } from 'vs/platform/list/browser/listService'; -import { OpenEditor } from 'vs/workbench/contrib/files/common/files'; +import { OpenEditor, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { toResource, SideBySideEditor, IEditorIdentifier } from 'vs/workbench/common/editor'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -53,19 +53,15 @@ export function getResourceForCommand(resource: URI | object | undefined, listSe return editorService.activeEditor ? toResource(editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER }) : undefined; } -export function getMultiSelectedResources(resource: URI | object | undefined, listService: IListService, editorService: IEditorService): Array { +export function getMultiSelectedResources(resource: URI | object | undefined, listService: IListService, editorService: IEditorService, explorerService: IExplorerService): Array { const list = listService.lastFocusedList; if (list?.getHTMLElement() === document.activeElement) { // Explorer if (list instanceof AsyncDataTree) { - const selection = list.getSelection().map((fs: ExplorerItem) => fs.resource); - const focusedElements = list.getFocus(); - const focus = focusedElements.length ? focusedElements[0] : undefined; - const mainUriStr = URI.isUri(resource) ? resource.toString() : focus instanceof ExplorerItem ? focus.resource.toString() : undefined; - // If the resource is passed it has to be a part of the returned context. - // We only respect the selection if it contains the focused element. - if (selection.some(s => URI.isUri(s) && s.toString() === mainUriStr)) { - return selection; + // Explorer + const context = explorerService.getContext(true); + if (context.length) { + return context.map(c => c.resource); } } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 42474dd76f..bbf7f2dc87 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -297,6 +297,13 @@ export class ExplorerView extends ViewletPane { for (const stat of this.tree.getSelection()) { const controller = this.renderer.getCompressedNavigationController(stat); + if (controller && focusedStat && controller === this.compressedNavigationController) { + if (stat === focusedStat) { + selectedStats.push(stat); + } + // Ignore stats which are selected but are part of the same compact node as the focused stat + continue; + } if (controller) { selectedStats.push(...controller.items); diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index d1a400c5bf..9705d766f6 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -197,7 +197,9 @@ export class ExplorerItem { // Properties local.resource = disk.resource; - local.updateName(disk.name); + if (!local.isRoot) { + local.updateName(disk.name); + } local._isDirectory = disk.isDirectory; local._mtime = disk.mtime; local._isDirectoryResolved = disk._isDirectoryResolved; diff --git a/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts index 70f22266a0..085654bf16 100644 --- a/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts @@ -24,6 +24,7 @@ import { appendToCommandPalette, appendEditorTitleContextMenuItem } from 'vs/wor import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { ShowOpenedFileInNewWindow } from 'vs/workbench/contrib/files/browser/fileActions'; +import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; const REVEAL_IN_OS_COMMAND_ID = 'revealFileInOS'; const REVEAL_IN_OS_LABEL = isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"); @@ -37,7 +38,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_R }, handler: (accessor: ServicesAccessor, resource: URI | object) => { - const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)); + const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService), accessor.get(IExplorerService)); revealResourcesInOS(resources, accessor.get(IElectronService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); } }); diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 416ce8088f..a1dbd1ca95 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -12,7 +12,7 @@ import { SetLogLevelAction, OpenWindowSessionLogFileAction } from 'vs/workbench/ import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; +import { IFileService, FileChangeType, whenProviderRegistered } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/contrib/output/common/output'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -71,6 +71,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { } private async registerLogChannel(id: string, label: string, file: URI): Promise { + await whenProviderRegistered(file, this.fileService); const outputChannelRegistry = Registry.as(OutputExt.OutputChannels); const exists = await this.fileService.exists(file); if (exists) { diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index 40717bf147..69ecb61714 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -37,7 +37,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { Separator, ActionViewItem, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { domEvent } from 'vs/base/browser/event'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IMarker } from 'vs/platform/markers/common/markers'; @@ -46,6 +46,7 @@ import { MementoObject } from 'vs/workbench/common/memento'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; +import { KeyCode } from 'vs/base/common/keyCodes'; function createResourceMarkersIterator(resourceMarkers: ResourceMarkers): Iterator> { const markersIt = Iterator.fromArray(resourceMarkers.markers); @@ -439,6 +440,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { })); this._register(this.tree.onDidChangeSelection(() => this.onSelected())); this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => { + this.reportFilteringUsed(); if (event.activeFile) { this.refreshPanel(); } else if (event.filterText || event.excludedFiles || event.showWarnings || event.showErrors || event.showInfos) { @@ -520,10 +522,14 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { if (filtered === 0) { this.messageBoxContainer.style.display = 'block'; this.messageBoxContainer.setAttribute('tabIndex', '0'); - if (total > 0) { - this.renderFilteredByFilterMessage(this.messageBoxContainer); + if (this.filterAction.activeFile) { + this.renderFilterMessageForActiveFile(this.messageBoxContainer); } else { - this.renderNoProblemsMessage(this.messageBoxContainer); + if (total > 0) { + this.renderFilteredByFilterMessage(this.messageBoxContainer); + } else { + this.renderNoProblemsMessage(this.messageBoxContainer); + } } } else { this.messageBoxContainer.style.display = 'none'; @@ -536,18 +542,52 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } } + private renderFilterMessageForActiveFile(container: HTMLElement): void { + if (this.currentActiveResource && this.markersWorkbenchService.markersModel.getResourceMarkers(this.currentActiveResource)) { + this.renderFilteredByFilterMessage(container); + } else { + this.renderNoProblemsMessageForActiveFile(container); + } + } + private renderFilteredByFilterMessage(container: HTMLElement) { const span1 = dom.append(container, dom.$('span')); span1.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS; + const link = dom.append(container, dom.$('a.messageAction')); + link.textContent = localize('clearFilter', "Clear Filters"); + link.setAttribute('tabIndex', '0'); + const span2 = dom.append(container, dom.$('span')); + span2.textContent = '.'; + dom.addStandardDisposableListener(link, dom.EventType.CLICK, () => this.clearFilters()); + dom.addStandardDisposableListener(link, dom.EventType.KEY_DOWN, (e: IKeyboardEvent) => { + if (e.equals(KeyCode.Enter) || e.equals(KeyCode.Space)) { + this.clearFilters(); + e.stopPropagation(); + } + }); this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_FILTERS); } + private renderNoProblemsMessageForActiveFile(container: HTMLElement) { + const span = dom.append(container, dom.$('span')); + span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT; + this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT); + } + private renderNoProblemsMessage(container: HTMLElement) { const span = dom.append(container, dom.$('span')); span.textContent = Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT; this.ariaLabelElement.setAttribute('aria-label', Messages.MARKERS_PANEL_NO_PROBLEMS_BUILT); } + private clearFilters(): void { + this.filterAction.filterText = ''; + this.filterAction.excludedFiles = false; + this.filterAction.showErrors = true; + this.filterAction.showWarnings = true; + this.filterAction.showInfos = true; + } + private autoReveal(focus: boolean = false): void { // No need to auto reveal if active file filter is on if (this.filterAction.activeFile) { @@ -714,6 +754,26 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { return { source, code }; } + private reportFilteringUsed(): void { + const data = { + errors: this.filterAction.showErrors, + warnings: this.filterAction.showWarnings, + infos: this.filterAction.showInfos, + activeFile: this.filterAction.activeFile, + excludedFiles: this.filterAction.excludedFiles, + }; + /* __GDPR__ + "problems.filter" : { + "errors" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "warnings": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "infos": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "activeFile": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "excludedFiles": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } + */ + this.telemetryService.publicLog('problems.filter', data); + } + protected saveState(): void { this.panelState['filter'] = this.filterAction.filterText; this.panelState['filterHistory'] = this.filterAction.filterHistory; diff --git a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts index 3910f09d18..4d9c062ab9 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts @@ -27,7 +27,6 @@ import { Marker } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; @@ -285,7 +284,6 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextViewService private readonly contextViewService: IContextViewService, @IThemeService private readonly themeService: IThemeService, - @ITelemetryService private readonly telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService ) { super(null, action); @@ -385,7 +383,6 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { inputbox.addToHistory(); this.action.filterText = inputbox.value; this.action.filterHistory = inputbox.getHistory(); - this.reportFilteringUsed(); } private updateBadge(): void { @@ -426,23 +423,6 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { } } - private reportFilteringUsed(): void { - const filterOptions = this.filterController.getFilterOptions(); - const data = { - errors: filterOptions.showErrors, - warnings: filterOptions.showWarnings, - infos: filterOptions.showInfos, - }; - /* __GDPR__ - "problems.filter" : { - "errors" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "warnings": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "infos": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - this.telemetryService.publicLog('problems.filter', data); - } - protected updateClass(): void { if (this.element && this.container) { this.element.className = this.action.class || ''; diff --git a/src/vs/workbench/contrib/markers/browser/messages.ts b/src/vs/workbench/contrib/markers/browser/messages.ts index baf1d1c2c2..6f3030968b 100644 --- a/src/vs/workbench/contrib/markers/browser/messages.ts +++ b/src/vs/workbench/contrib/markers/browser/messages.ts @@ -21,6 +21,7 @@ export default class Messages { public static MARKERS_PANEL_TITLE_PROBLEMS: string = nls.localize('markers.panel.title.problems', "Problems"); public static MARKERS_PANEL_NO_PROBLEMS_BUILT: string = nls.localize('markers.panel.no.problems.build', "No problems have been detected in the workspace so far."); + public static MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT: string = nls.localize('markers.panel.no.problems.activeFile.build', "No problems have been detected in the current file so far."); public static MARKERS_PANEL_NO_PROBLEMS_FILTERS: string = nls.localize('markers.panel.no.problems.filters', "No results found with provided filter criteria."); public static MARKERS_PANEL_ACTION_TOOLTIP_MORE_FILTERS: string = nls.localize('markers.panel.action.moreFilters', "More Filters..."); diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts index 1c24bae37c..a263d50936 100644 --- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts @@ -3,23 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { isMacintosh, isNative } from 'vs/base/common/platform'; +import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWindowsConfiguration } from 'vs/platform/windows/common/windows'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { localize } from 'vs/nls'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { RunOnceScheduler } from 'vs/base/common/async'; -import { URI } from 'vs/base/common/uri'; -import { isEqual } from 'vs/base/common/resources'; -import { isMacintosh, isNative } from 'vs/base/common/platform'; -import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IProductService } from 'vs/platform/product/common/productService'; interface IConfiguration extends IWindowsConfiguration { update: { mode: string; }; @@ -132,82 +126,5 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo } } -export class WorkspaceChangeExtHostRelauncher extends Disposable implements IWorkbenchContribution { - - private firstFolderResource?: URI; - private extensionHostRestarter: RunOnceScheduler; - - private onDidChangeWorkspaceFoldersUnbind: IDisposable | undefined; - - constructor( - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IExtensionService extensionService: IExtensionService, - @IHostService hostService: IHostService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService - ) { - super(); - - this.extensionHostRestarter = this._register(new RunOnceScheduler(() => { - if (!!environmentService.extensionTestsLocationURI) { - return; // no restart when in tests: see https://github.com/Microsoft/vscode/issues/66936 - } - - if (environmentService.configuration.remoteAuthority) { - hostService.reload(); // TODO@aeschli, workaround - } else if (isNative) { - extensionService.restartExtensionHost(); - } - }, 10)); - - this.contextService.getCompleteWorkspace() - .then(workspace => { - this.firstFolderResource = workspace.folders.length > 0 ? workspace.folders[0].uri : undefined; - this.handleWorkbenchState(); - this._register(this.contextService.onDidChangeWorkbenchState(() => setTimeout(() => this.handleWorkbenchState()))); - }); - - this._register(toDisposable(() => { - if (this.onDidChangeWorkspaceFoldersUnbind) { - this.onDidChangeWorkspaceFoldersUnbind.dispose(); - } - })); - } - - private handleWorkbenchState(): void { - - // React to folder changes when we are in workspace state - if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { - - // Update our known first folder path if we entered workspace - const workspace = this.contextService.getWorkspace(); - this.firstFolderResource = workspace.folders.length > 0 ? workspace.folders[0].uri : undefined; - - // Install workspace folder listener - if (!this.onDidChangeWorkspaceFoldersUnbind) { - this.onDidChangeWorkspaceFoldersUnbind = this.contextService.onDidChangeWorkspaceFolders(() => this.onDidChangeWorkspaceFolders()); - } - } - - // Ignore the workspace folder changes in EMPTY or FOLDER state - else { - dispose(this.onDidChangeWorkspaceFoldersUnbind); - this.onDidChangeWorkspaceFoldersUnbind = undefined; - } - } - - private onDidChangeWorkspaceFolders(): void { - const workspace = this.contextService.getWorkspace(); - - // Restart extension host if first root folder changed (impact on deprecated workspace.rootPath API) - const newFirstFolderResource = workspace.folders.length > 0 ? workspace.folders[0].uri : undefined; - if (!isEqual(this.firstFolderResource, newFirstFolderResource)) { - this.firstFolderResource = newFirstFolderResource; - - this.extensionHostRestarter.schedule(); // buffer calls to extension host restart - } - } -} - const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(SettingsChangeRelauncher, LifecyclePhase.Restored); -workbenchRegistry.registerWorkbenchContribution(WorkspaceChangeExtHostRelauncher, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts index b7b4066a75..05e70c035e 100644 --- a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts +++ b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts @@ -20,6 +20,7 @@ import { startsWith } from 'vs/base/common/strings'; import { isStringArray } from 'vs/base/common/types'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export interface IRemoteSelectItem extends ISelectOptionItem { authority: string[]; @@ -85,10 +86,10 @@ export class SwitchRemoteViewItem extends SelectActionViewItem { return this.optionsItems[index]; } - static createOptionItems(views: IViewDescriptor[]): IRemoteSelectItem[] { + static createOptionItems(views: IViewDescriptor[], contextKeyService: IContextKeyService): IRemoteSelectItem[] { let options: IRemoteSelectItem[] = []; views.forEach(view => { - if (view.group && startsWith(view.group, 'targets') && view.remoteAuthority) { + if (view.group && startsWith(view.group, 'targets') && view.remoteAuthority && (!view.when || contextKeyService.contextMatchesRules(view.when))) { options.push({ text: view.name, authority: isStringArray(view.remoteAuthority) ? view.remoteAuthority : [view.remoteAuthority] }); } }); diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 33b76f6db1..56fec6fd88 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -72,7 +72,8 @@ class HelpModel { })), quickInputService, environmentService, - openerService + openerService, + remoteExplorerService )); } @@ -89,7 +90,8 @@ class HelpModel { })), quickInputService, environmentService, - openerService + openerService, + remoteExplorerService )); } @@ -106,7 +108,8 @@ class HelpModel { })), quickInputService, environmentService, - openerService + openerService, + remoteExplorerService )); } @@ -123,7 +126,8 @@ class HelpModel { })), quickInputService, environmentService, - openerService + openerService, + remoteExplorerService )); } @@ -137,7 +141,8 @@ class HelpModel { })), quickInputService, environmentService, - commandService + commandService, + remoteExplorerService )); } @@ -158,14 +163,15 @@ abstract class HelpItemBase implements IHelpItem { public label: string, public values: { extensionDescription: IExtensionDescription, url?: string, remoteAuthority: string[] | undefined }[], private quickInputService: IQuickInputService, - private environmentService: IWorkbenchEnvironmentService + private environmentService: IWorkbenchEnvironmentService, + private remoteExplorerService: IRemoteExplorerService ) { iconClasses.push('remote-help-tree-node-item-icon'); } async handleClick() { const remoteAuthority = this.environmentService.configuration.remoteAuthority; - if (remoteAuthority) { + if (remoteAuthority && startsWith(remoteAuthority, this.remoteExplorerService.targetType)) { for (let value of this.values) { if (value.remoteAuthority) { for (let authority of value.remoteAuthority) { @@ -207,9 +213,10 @@ class HelpItem extends HelpItemBase { values: { extensionDescription: IExtensionDescription; url: string, remoteAuthority: string[] | undefined }[], quickInputService: IQuickInputService, environmentService: IWorkbenchEnvironmentService, - private openerService: IOpenerService + private openerService: IOpenerService, + remoteExplorerService: IRemoteExplorerService ) { - super(iconClasses, label, values, quickInputService, environmentService); + super(iconClasses, label, values, quickInputService, environmentService, remoteExplorerService); } protected async takeAction(extensionDescription: IExtensionDescription, url: string): Promise { @@ -225,8 +232,9 @@ class IssueReporterItem extends HelpItemBase { quickInputService: IQuickInputService, environmentService: IWorkbenchEnvironmentService, private commandService: ICommandService, + remoteExplorerService: IRemoteExplorerService ) { - super(iconClasses, label, values, quickInputService, environmentService); + super(iconClasses, label, values, quickInputService, environmentService, remoteExplorerService); } protected async takeAction(extensionDescription: IExtensionDescription): Promise { @@ -236,7 +244,7 @@ class IssueReporterItem extends HelpItemBase { class HelpAction extends Action { static readonly ID = 'remote.explorer.help'; - static readonly LABEL = nls.localize('remote.explorer.help', "Help and Feedback"); + static readonly LABEL = nls.localize('remote.explorer.help', "Help, Documentation, and Feedback"); private helpModel: HelpModel; constructor(id: string, @@ -280,6 +288,7 @@ export class RemoteViewlet extends FilterViewContainerViewlet { @IExtensionService extensionService: IExtensionService, @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); } @@ -290,7 +299,7 @@ export class RemoteViewlet extends FilterViewContainerViewlet { public getActionViewItem(action: Action): IActionViewItem | undefined { if (action.id === SwitchRemoteAction.ID) { - return this.instantiationService.createInstance(SwitchRemoteViewItem, action, SwitchRemoteViewItem.createOptionItems(Registry.as(Extensions.ViewsRegistry).getViews(VIEW_CONTAINER))); + return this.instantiationService.createInstance(SwitchRemoteViewItem, action, SwitchRemoteViewItem.createOptionItems(Registry.as(Extensions.ViewsRegistry).getViews(VIEW_CONTAINER), this.contextKeyService)); } return super.getActionViewItem(action); diff --git a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css index 55384a1d20..d287b989be 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css +++ b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css @@ -29,11 +29,15 @@ margin-top: 7px; } +.monaco-workbench.mac .part > .title > .title-actions .switch-remote { + border-radius: 4px; +} + .switch-remote > .monaco-select-box { border: none; display: block; } .monaco-workbench .part > .title > .title-actions .switch-remote > .monaco-select-box { - padding-left: 3px; + padding: 0 22px 0 6px; } diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index c9d711927a..6f4ddd3d95 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -809,37 +809,37 @@ export class DirtyDiffController extends Disposable implements IEditorContributi export const editorGutterModifiedBackground = registerColor('editorGutter.modifiedBackground', { dark: new Color(new RGBA(12, 125, 157)), light: new Color(new RGBA(102, 175, 224)), - hc: new Color(new RGBA(0, 73, 122)) + hc: new Color(new RGBA(0, 155, 249)) }, nls.localize('editorGutterModifiedBackground', "Editor gutter background color for lines that are modified.")); export const editorGutterAddedBackground = registerColor('editorGutter.addedBackground', { dark: new Color(new RGBA(88, 124, 12)), light: new Color(new RGBA(129, 184, 139)), - hc: new Color(new RGBA(27, 82, 37)) + hc: new Color(new RGBA(51, 171, 78)) }, nls.localize('editorGutterAddedBackground', "Editor gutter background color for lines that are added.")); export const editorGutterDeletedBackground = registerColor('editorGutter.deletedBackground', { dark: new Color(new RGBA(148, 21, 27)), light: new Color(new RGBA(202, 75, 81)), - hc: new Color(new RGBA(141, 14, 20)) + hc: new Color(new RGBA(252, 93, 109)) }, nls.localize('editorGutterDeletedBackground', "Editor gutter background color for lines that are deleted.")); export const minimapGutterModifiedBackground = registerColor('minimapGutter.modifiedBackground', { dark: new Color(new RGBA(12, 125, 157)), light: new Color(new RGBA(102, 175, 224)), - hc: new Color(new RGBA(0, 73, 122)) + hc: new Color(new RGBA(0, 155, 249)) }, nls.localize('minimapGutterModifiedBackground', "Minimap gutter background color for lines that are modified.")); export const minimapGutterAddedBackground = registerColor('minimapGutter.addedBackground', { dark: new Color(new RGBA(88, 124, 12)), light: new Color(new RGBA(129, 184, 139)), - hc: new Color(new RGBA(27, 82, 37)) + hc: new Color(new RGBA(51, 171, 78)) }, nls.localize('minimapGutterAddedBackground', "Minimap gutter background color for lines that are added.")); export const minimapGutterDeletedBackground = registerColor('minimapGutter.deletedBackground', { dark: new Color(new RGBA(148, 21, 27)), light: new Color(new RGBA(202, 75, 81)), - hc: new Color(new RGBA(141, 14, 20)) + hc: new Color(new RGBA(252, 93, 109)) }, nls.localize('minimapGutterDeletedBackground', "Minimap gutter background color for lines that are deleted.")); const overviewRulerDefault = new Color(new RGBA(0, 122, 204, 0.6)); diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 84b22a17f2..156887639b 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -81,6 +81,13 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'scm.diffDecorations': { type: 'string', enum: ['all', 'gutter', 'overview', 'minimap', 'none'], + enumDescriptions: [ + localize('scm.diffDecorations.all', "Show the diff decorations in all available locations."), + localize('scm.diffDecorations.gutter', "Show the diff decorations only in the editor gutter."), + localize('scm.diffDecorations.overviewRuler', "Show the diff decorations only in the overview ruler."), + localize('scm.diffDecorations.minimap', "Show the diff decorations only in the minimap."), + localize('scm.diffDecorations.none', "Do not show the diff decorations.") + ], default: 'all', description: localize('diffDecorations', "Controls diff decorations in the editor.") }, diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 6d278b13c1..81433e4d9d 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -149,12 +149,6 @@ export class PatternInputWidget extends Widget { this._register(attachInputBoxStyler(this.inputBox, this.themeService)); this.inputFocusTracker = dom.trackFocus(this.inputBox.inputElement); this.onkeyup(this.inputBox.inputElement, (keyboardEvent) => this.onInputKeyUp(keyboardEvent)); - this._register(this.inputBox.onDidChange(() => { - if (this.searchConfig.searchOnType) { - this._onCancel.fire(); - this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(true), this.searchConfig.searchOnTypeDebouncePeriod); - } - })); const controls = document.createElement('div'); controls.className = 'controls'; @@ -176,6 +170,10 @@ export class PatternInputWidget extends Widget { this._onCancel.fire(); return; default: + if (this.searchConfig.searchOnType) { + this._onCancel.fire(); + this.searchOnTypeDelayer.trigger(() => this._onSubmit.fire(true), this.searchConfig.searchOnTypeDebouncePeriod); + } return; } } diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 4ae03845a4..11774d33d4 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -41,7 +41,7 @@ import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler'; import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; -import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, RerunEditorSearchAction } from 'vs/workbench/contrib/search/browser/searchActions'; +import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, RerunEditorSearchAction, RerunEditorSearchWithContextAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; import { SearchView, SearchViewPosition } from 'vs/workbench/contrib/search/browser/searchView'; import { SearchViewlet } from 'vs/workbench/contrib/search/browser/searchViewlet'; @@ -396,7 +396,7 @@ const searchInFolderCommand: ICommandHandler = (accessor, resource?: URI) => { const panelService = accessor.get(IPanelService); const fileService = accessor.get(IFileService); const configurationService = accessor.get(IConfigurationService); - const resources = getMultiSelectedResources(resource, listService, accessor.get(IEditorService)); + const resources = getMultiSelectedResources(resource, listService, accessor.get(IEditorService), accessor.get(IExplorerService)); return openSearchView(viewletService, panelService, configurationService, true).then(searchView => { if (resources && resources.length && searchView) { @@ -651,6 +651,11 @@ registry.registerWorkbenchAction( 'Search Editor: Search Again', category, ContextKeyExpr.and(EditorContextKeys.languageId.isEqualTo('search-result'))); +registry.registerWorkbenchAction( + SyncActionDescriptor.create(RerunEditorSearchWithContextAction, RerunEditorSearchWithContextAction.ID, RerunEditorSearchWithContextAction.LABEL), + 'Search Editor: Search Again (With Context)', category, + ContextKeyExpr.and(EditorContextKeys.languageId.isEqualTo('search-result'))); + // Register Quick Open Handler Registry.as(QuickOpenExtensions.Quickopen).registerDefaultQuickOpenHandler( @@ -690,7 +695,7 @@ configurationRegistry.registerConfiguration({ 'search.exclude': { type: 'object', markdownDescription: nls.localize('exclude', "Configure glob patterns for excluding files and folders in searches. Inherits all glob patterns from the `#files.exclude#` setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), - default: { '**/node_modules': true, '**/bower_components': true }, + default: { '**/node_modules': true, '**/bower_components': true, '**/*.code-search': true }, additionalProperties: { anyOf: [ { diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index c625a2f167..50d77941cc 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -32,6 +32,7 @@ import { ITreeNavigator } from 'vs/base/browser/ui/tree/tree'; import { createEditorFromSearchResult, refreshActiveEditorSearch } from 'vs/workbench/contrib/search/browser/searchEditor'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export function isSearchViewFocused(viewletService: IViewletService, panelService: IPanelService): boolean { const searchView = getSearchView(viewletService, panelService); @@ -472,7 +473,38 @@ export class RerunEditorSearchAction extends Action { async run() { if (this.configurationService.getValue('search').enableSearchEditorPreview) { await this.progressService.withProgress({ location: ProgressLocation.Window }, - () => refreshActiveEditorSearch(this.editorService, this.instantiationService, this.contextService, this.labelService, this.configurationService)); + () => refreshActiveEditorSearch(undefined, this.editorService, this.instantiationService, this.contextService, this.labelService, this.configurationService)); + } + } +} + +export class RerunEditorSearchWithContextAction extends Action { + + static readonly ID: string = Constants.RerunEditorSearchWithContextCommandId; + static readonly LABEL = nls.localize('search.rerunEditorSearchContext', "Search Again (With Context)"); + + constructor(id: string, label: string, + @IInstantiationService private instantiationService: IInstantiationService, + @IEditorService private editorService: IEditorService, + @IConfigurationService private configurationService: IConfigurationService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @ILabelService private labelService: ILabelService, + @IProgressService private progressService: IProgressService, + @IQuickInputService private quickPickService: IQuickInputService + ) { + super(id, label); + } + + async run() { + const lines = await this.quickPickService.input({ + prompt: nls.localize('lines', "Lines of Context"), + value: '2', + validateInput: async (value) => isNaN(parseInt(value)) ? nls.localize('mustBeInteger', "Must enter an integer") : undefined + }); + if (lines === undefined) { return; } + if (this.configurationService.getValue('search').enableSearchEditorPreview) { + await this.progressService.withProgress({ location: ProgressLocation.Window }, + () => refreshActiveEditorSearch(+lines, this.editorService, this.instantiationService, this.contextService, this.labelService, this.configurationService)); } } } diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 62969ab1c1..612b499f5e 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { ITextQuery, IPatternInfo, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import * as network from 'vs/base/common/network'; import { Range } from 'vs/editor/common/core/range'; -import { ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { ITextModel, TrackedRangeStickiness, EndOfLinePreference } from 'vs/editor/common/model'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; @@ -20,6 +20,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { searchEditorFindMatch, searchEditorFindMatchBorder } from 'vs/platform/theme/common/colorRegistry'; +import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput'; +import { localize } from 'vs/nls'; // Using \r\n on Windows inserts an extra newline between results. const lineDelimiter = '\n'; @@ -45,7 +47,7 @@ const matchToSearchResultFormat = (match: Match): { line: string, ranges: Range[ const prefix = ` ${lineNumber}: ${paddingStr}`; const prefixOffset = prefix.length; - const line = (prefix + sourceLine); + const line = (prefix + sourceLine).replace(/\r?\n?$/, ''); const rangeOnThisLine = ({ start, end }: { start?: number; end?: number; }) => new Range(1, (start ?? 1) + prefixOffset, 1, (end ?? sourceLine.length + 1) + prefixOffset); @@ -65,6 +67,7 @@ const matchToSearchResultFormat = (match: Match): { line: string, ranges: Range[ }; type SearchResultSerialization = { text: string[], matchRanges: Range[] }; + function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: URI) => string): SearchResultSerialization { const serializedMatches = flatten(fileMatch.matches() .sort(searchMatchComparer) @@ -76,17 +79,37 @@ function fileMatchToSearchResultFormat(fileMatch: FileMatch, labelFormatter: (x: const targetLineNumberToOffset: Record = {}; + const context: { line: string, lineNumber: number }[] = []; + fileMatch.context.forEach((line, lineNumber) => context.push({ line, lineNumber })); + context.sort((a, b) => a.lineNumber - b.lineNumber); + + let lastLine: number | undefined = undefined; + const seenLines = new Set(); serializedMatches.forEach(match => { if (!seenLines.has(match.line)) { + while (context.length && context[0].lineNumber < +match.lineNumber) { + const { line, lineNumber } = context.shift()!; + if (lastLine !== undefined && lineNumber !== lastLine + 1) { + text.push(''); + } + text.push(` ${lineNumber} ${line}`); + lastLine = lineNumber; + } + targetLineNumberToOffset[match.lineNumber] = text.length; seenLines.add(match.line); text.push(match.line); + lastLine = +match.lineNumber; } matchRanges.push(...match.ranges.map(translateRangeLines(targetLineNumberToOffset[match.lineNumber]))); }); + while (context.length) { + const { line, lineNumber } = context.shift()!; + text.push(` ${lineNumber} ${line}`); + } return { text, matchRanges }; } @@ -104,7 +127,7 @@ const flattenSearchResultSerializations = (serializations: SearchResultSerializa return { text, matchRanges }; }; -const contentPatternToSearchResultHeader = (pattern: ITextQuery | null, includes: string, excludes: string): string[] => { +const contentPatternToSearchResultHeader = (pattern: ITextQuery | null, includes: string, excludes: string, contextLines: number): string[] => { if (!pattern) { return []; } const removeNullFalseAndUndefined = (a: (T | null | false | undefined)[]) => a.filter(a => a !== false && a !== null && a !== undefined) as T[]; @@ -123,19 +146,56 @@ const contentPatternToSearchResultHeader = (pattern: ITextQuery | null, includes ]).join(' ')}`, includes ? `# Including: ${includes}` : undefined, excludes ? `# Excluding: ${excludes}` : undefined, + contextLines ? `# ContextLines: ${contextLines}` : undefined, '' ]); }; -const searchHeaderToContentPattern = (header: string[]): { pattern: string, flags: { regex: boolean, wholeWord: boolean, caseSensitive: boolean, ignoreExcludes: boolean }, includes: string, excludes: string } => { - const query = { + +type SearchHeader = { + pattern: string; + flags: { + regex: boolean; + wholeWord: boolean; + caseSensitive: boolean; + ignoreExcludes: boolean; + }; + includes: string; + excludes: string; + context: number | undefined; +}; + +const searchHeaderToContentPattern = (header: string[]): SearchHeader => { + const query: SearchHeader = { pattern: '', flags: { regex: false, caseSensitive: false, ignoreExcludes: false, wholeWord: false }, includes: '', - excludes: '' + excludes: '', + context: undefined }; - const unescapeNewlines = (str: string) => str.replace(/\\\\/g, '\\').replace(/\\n/g, '\n'); + const unescapeNewlines = (str: string) => { + let out = ''; + for (let i = 0; i < str.length; i++) { + if (str[i] === '\\') { + i++; + const escaped = str[i]; + + if (escaped === 'n') { + out += '\n'; + } + else if (escaped === '\\') { + out += '\\'; + } + else { + throw Error(localize('invalidQueryStringError', "All backslashes in Query string must be escaped (\\\\)")); + } + } else { + out += str[i]; + } + } + return out; + }; const parseYML = /^# ([^:]*): (.*)$/; for (const line of header) { const parsed = parseYML.exec(line); @@ -145,6 +205,7 @@ const searchHeaderToContentPattern = (header: string[]): { pattern: string, flag case 'Query': query.pattern = unescapeNewlines(value); break; case 'Including': query.includes = value; break; case 'Excluding': query.excludes = value; break; + case 'ContextLines': query.context = +value; break; case 'Flags': { query.flags = { regex: value.indexOf('RegExp') !== -1, @@ -159,25 +220,26 @@ const searchHeaderToContentPattern = (header: string[]): { pattern: string, flag return query; }; -const serializeSearchResultForEditor = (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, labelFormatter: (x: URI) => string): SearchResultSerialization => { - const header = contentPatternToSearchResultHeader(searchResult.query, rawIncludePattern, rawExcludePattern); +const serializeSearchResultForEditor = (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, contextLines: number, labelFormatter: (x: URI) => string): SearchResultSerialization => { + const header = contentPatternToSearchResultHeader(searchResult.query, rawIncludePattern, rawExcludePattern, contextLines); const allResults = flattenSearchResultSerializations( - flatten(searchResult.folderMatches().sort(searchMatchComparer) - .map(folderMatch => folderMatch.matches().sort(searchMatchComparer) - .map(fileMatch => fileMatchToSearchResultFormat(fileMatch, labelFormatter))))); + flatten( + searchResult.folderMatches().sort(searchMatchComparer) + .map(folderMatch => folderMatch.matches().sort(searchMatchComparer) + .map(fileMatch => fileMatchToSearchResultFormat(fileMatch, labelFormatter))))); return { matchRanges: allResults.matchRanges.map(translateRangeLines(header.length)), text: header.concat(allResults.text) }; }; export const refreshActiveEditorSearch = - async (editorService: IEditorService, instantiationService: IInstantiationService, contextService: IWorkspaceContextService, labelService: ILabelService, configurationService: IConfigurationService) => { + async (contextLines: number | undefined, editorService: IEditorService, instantiationService: IInstantiationService, contextService: IWorkspaceContextService, labelService: ILabelService, configurationService: IConfigurationService) => { const model = editorService.activeTextEditorWidget?.getModel(); if (!model) { return; } const textModel = model as ITextModel; - const header = textModel.getValueInRange(new Range(1, 1, 5, 1)) + const header = textModel.getValueInRange(new Range(1, 1, 5, 1), EndOfLinePreference.LF) .split(lineDelimiter) .filter(line => line.indexOf('# ') === 0); @@ -190,6 +252,8 @@ export const refreshActiveEditorSearch = isWordMatch: contentPattern.flags.wholeWord }; + contextLines = contextLines ?? contentPattern.context ?? 0; + const options: ITextQueryBuilderOptions = { _reason: 'searchEditor', extraFileResources: instantiationService.invokeFunction(getOutOfWorkspaceEditorResources), @@ -202,6 +266,8 @@ export const refreshActiveEditorSearch = matchLines: 1, charsPerLine: 1000 }, + afterContext: contextLines, + beforeContext: contextLines, isSmartCase: configurationService.getValue('search').smartCase, expandPatterns: true }; @@ -220,7 +286,7 @@ export const refreshActiveEditorSearch = await searchModel.search(query); const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchModel.searchResult, '', '', labelFormatter); + const results = serializeSearchResultForEditor(searchModel.searchResult, contentPattern.includes, contentPattern.excludes, contextLines, labelFormatter); textModel.setValue(results.text.join(lineDelimiter)); textModel.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); @@ -233,29 +299,35 @@ export const createEditorFromSearchResult = const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); - const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, labelFormatter); - + const results = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter); + const contents = results.text.join(lineDelimiter); let possible = { - contents: results.text.join(lineDelimiter), + contents, mode: 'search-result', resource: URI.from({ scheme: network.Schemas.untitled, path: searchTerm }) }; let id = 0; - while (editorService.getOpened(possible)) { + let existing = editorService.getOpened(possible); + while (existing) { + if (existing instanceof UntitledTextEditorInput) { + const model = await existing.resolve(); + const existingContents = model.textEditorModel.getValue(EndOfLinePreference.LF); + if (existingContents === contents) { + break; + } + } possible.resource = possible.resource.with({ path: searchTerm + '-' + ++id }); + existing = editorService.getOpened(possible); } const editor = await editorService.openEditor(possible); const control = editor?.getControl()!; - control.updateOptions({ lineNumbers: 'off' }); - const model = control.getModel() as ITextModel; model.deltaDecorations([], results.matchRanges.map(range => ({ range, options: { className: 'searchEditorFindMatch', stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges } }))); }; -// theming registerThemingParticipant((theme, collector) => { collector.addRule(`.monaco-editor .searchEditorFindMatch { background-color: ${theme.getColor(searchEditorFindMatch)}; }`); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 4c63d2f5fb..eec9b25e75 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -206,7 +206,7 @@ export class SearchView extends ViewletPane { this.delayedRefresh = this._register(new Delayer(250)); - this.addToSearchHistoryDelayer = this._register(new Delayer(500)); + this.addToSearchHistoryDelayer = this._register(new Delayer(2000)); this.actions = [ this._register(this.instantiationService.createInstance(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL)), @@ -963,13 +963,15 @@ export class SearchView extends ViewletPane { return this.searchWidget && this.searchWidget.searchInput.getValue().length > 0; } - clearSearchResults(): void { + clearSearchResults(clearInput = true): void { this.viewModel.searchResult.clear(); this.showEmptyStage(true); if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.showSearchWithoutFolderMessage(); } - this.searchWidget.clear(); + if (clearInput) { + this.searchWidget.clear(); + } this.viewModel.cancelSearch(); this.updateActions(); @@ -1202,7 +1204,7 @@ export class SearchView extends ViewletPane { const useExcludesAndIgnoreFiles = this.inputPatternExcludes.useExcludesAndIgnoreFiles(); if (contentPattern.length === 0) { - this.clearSearchResults(); + this.clearSearchResults(false); this.clearMessage(); return; } diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 4860263ea8..035c72d41f 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -466,6 +466,7 @@ export class SearchWidget extends Widget { } if (keyboardEvent.equals(KeyCode.Enter)) { + this.searchInput.onSearchSubmit(); this.submitSearch(); keyboardEvent.preventDefault(); } diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index 9fa0b9d991..ae4dcfd2fc 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -17,6 +17,7 @@ export const CopyMatchCommandId = 'search.action.copyMatch'; export const CopyAllCommandId = 'search.action.copyAll'; export const OpenInEditorCommandId = 'search.action.openInEditor'; export const RerunEditorSearchCommandId = 'search.action.rerunEditorSearch'; +export const RerunEditorSearchWithContextCommandId = 'search.action.rerunEditorSearchWithContext'; export const ClearSearchHistoryCommandId = 'search.action.clearHistory'; export const FocusSearchListCommandID = 'search.action.focusSearchList'; export const ReplaceActionId = 'search.action.replace'; diff --git a/src/vs/workbench/contrib/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts index 8e2507e461..3d9228cda6 100644 --- a/src/vs/workbench/contrib/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -20,7 +20,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgress, IProgressStep } from 'vs/platform/progress/common/progress'; import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; -import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchConfigurationProperties, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange } from 'vs/workbench/services/search/common/search'; +import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchConfigurationProperties, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange, ITextSearchContext, ITextSearchResult } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { overviewRulerFindMatchForeground, minimapFindMatch } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; @@ -197,6 +197,11 @@ export class FileMatch extends Disposable implements IFileMatch { private _updateScheduler: RunOnceScheduler; private _modelDecorations: string[] = []; + private _context: Map = new Map(); + public get context(): Map { + return new Map(this._context); + } + constructor(private _query: IPatternInfo, private _previewOptions: ITextSearchPreviewOptions | undefined, private _maxResults: number | undefined, private _parent: FolderMatch, private rawMatch: IFileMatch, @IModelService private readonly modelService: IModelService, @IReplaceService private readonly replaceService: IReplaceService ) { @@ -221,6 +226,8 @@ export class FileMatch extends Disposable implements IFileMatch { textSearchResultToMatches(rawMatch, this) .forEach(m => this.add(m)); }); + + this.addContext(this.rawMatch.results); } } @@ -375,6 +382,14 @@ export class FileMatch extends Disposable implements IFileMatch { return getBaseLabel(this.resource); } + addContext(results: ITextSearchResult[] | undefined) { + if (!results) { return; } + + results + .filter((result => !resultIsMatch(result)) as ((a: any) => a is ITextSearchContext)) + .forEach(context => this._context.set(context.lineNumber, context.text)); + } + add(match: Match, trigger?: boolean) { this._matches.set(match.id(), match); if (trigger) { @@ -479,6 +494,8 @@ export class FolderMatch extends Disposable { .forEach(m => existingFileMatch.add(m)); }); updated.push(existingFileMatch); + + existingFileMatch.addContext(rawFileMatch.results); } else { const fileMatch = this.instantiationService.createInstance(FileMatch, this._query.contentPattern, this._query.previewOptions, this._query.maxResults, this, rawFileMatch); this.doAdd(fileMatch); @@ -965,11 +982,6 @@ export class SearchModel extends Disposable { search(query: ITextQuery, onProgress?: (result: ISearchProgressItem) => void): Promise { this.cancelSearch(); - // Exclude Search Editor results unless explicity included - const searchEditorFilenameGlob = `**/*.code-search`; - if (!query.includePattern || !query.includePattern[searchEditorFilenameGlob]) { - query.excludePattern = { ...(query.excludePattern ?? {}), [searchEditorFilenameGlob]: true }; - } this._searchQuery = query; if (!this.searchConfig.searchOnType) { @@ -982,7 +994,7 @@ export class SearchModel extends Disposable { this._replacePattern = new ReplacePattern(this.replaceString, this._searchQuery.contentPattern); // In search on type case, delay the streaming of results just a bit, so that we don't flash the only "local results" fast path - this._startStreamDelay = new Promise(resolve => setTimeout(resolve, this.searchConfig.searchOnType ? 100 : 0)); + this._startStreamDelay = new Promise(resolve => setTimeout(resolve, this.searchConfig.searchOnType ? 150 : 0)); const tokenSource = this.currentCancelTokenSource = new CancellationTokenSource(); const currentRequest = this.searchService.textSearch(this._searchQuery, this.currentCancelTokenSource.token, p => { diff --git a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts index 9cb242eaf0..5a38c22c52 100644 --- a/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts +++ b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts @@ -33,7 +33,7 @@ const taskDefinitionSchema: IJSONSchema = { type: 'object', description: nls.localize('TaskDefinition.properties', 'Additional properties of the task type'), additionalProperties: { - $ref: 'http://json-schema.org/draft-04/schema#' + $ref: 'http://json-schema.org/draft-07/schema#' } } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index c8438e912f..790ea4d8cc 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -170,7 +170,7 @@ configurationRegistry.registerConfiguration({ default: DEFAULT_LINE_HEIGHT }, 'terminal.integrated.minimumContrastRatio': { - description: nls.localize('terminal.integrated.minimumContrastRatio', "When set the foreground color of each cell will change to try meet the contrast ratio specified. Example values:\n\n- 1: The default, do nothing.\n- 4.5: Minimum for WCAG AA compliance.\n- 7: Minimum for WCAG AAA compliance.\n- 21: White on black or black on white."), + markdownDescription: nls.localize('terminal.integrated.minimumContrastRatio', "When set the foreground color of each cell will change to try meet the contrast ratio specified. Example values:\n\n- 1: The default, do nothing.\n- 4.5: Minimum for [WCAG AA compliance](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html).\n- 7: Minimum for [WCAG AAA compliance](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast7.html).\n- 21: White on black or black on white."), type: 'number', default: 1 }, @@ -205,7 +205,7 @@ configurationRegistry.registerConfiguration({ markdownDescription: nls.localize('terminal.integrated.detectLocale', "Controls whether to detect and set the `$LANG` environment variable to a UTF-8 compliant option since Azure Data Studio's terminal only supports UTF-8 encoded data coming from the shell."), // {{SQL CARBON EDIT}} Change product name to ADS type: 'string', enum: ['auto', 'off', 'on'], - enumDescriptions: [ + markdownEnumDescriptions: [ nls.localize('terminal.integrated.detectLocale.auto', "Set the `$LANG` environment variable if the existing variable does not exist or it does not end in `'.UTF-8'`."), nls.localize('terminal.integrated.detectLocale.off', "Do not set the `$LANG` environment variable."), nls.localize('terminal.integrated.detectLocale.on', "Always set the `$LANG` environment variable.") @@ -215,8 +215,8 @@ configurationRegistry.registerConfiguration({ 'terminal.integrated.rendererType': { type: 'string', enum: ['auto', 'canvas', 'dom', 'experimentalWebgl'], - enumDescriptions: [ - nls.localize('terminal.integrated.rendererType.auto', "Let Azure Data Studio which renderer to use."), // {{SQL CARBON EDIT}} Change product name to ADS + markdownEnumDescriptions: [ + nls.localize('terminal.integrated.rendererType.auto', "Let Azure Data Studio guess which renderer to use."), // {{SQL CARBON EDIT}} Change product name to ADS nls.localize('terminal.integrated.rendererType.canvas', "Use the standard GPU/canvas-based renderer."), nls.localize('terminal.integrated.rendererType.dom', "Use the fallback DOM-based renderer."), nls.localize('terminal.integrated.rendererType.experimentalWebgl', "Use the experimental webgl-based renderer. Note that this has some [known issues](https://github.com/xtermjs/xterm.js/issues?q=is%3Aopen+is%3Aissue+label%3Aarea%2Faddon%2Fwebgl) and this will only be enabled for new terminals (not hot swappable like the other renderers).") @@ -252,7 +252,7 @@ configurationRegistry.registerConfiguration({ default: false }, 'terminal.integrated.commandsToSkipShell': { - description: nls.localize('terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell and instead always be handled by Code. This allows the use of keybindings that would normally be consumed by the shell to act the same as when the terminal is not focused, for example ctrl+p to launch Quick Open.\nDefault Skipped Commands:\n\n{0}", DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n')), + markdownDescription: nls.localize('terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell and instead always be handled by Code. This allows the use of keybindings that would normally be consumed by the shell to act the same as when the terminal is not focused, for example ctrl+p to launch Quick Open.\nDefault Skipped Commands:\n\n{0}", DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n')), type: 'array', items: { type: 'string' @@ -260,7 +260,7 @@ configurationRegistry.registerConfiguration({ default: [] }, 'terminal.integrated.allowChords': { - markdownDescription: nls.localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass `terminal.integrated.commandsToSkipShell`, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code)."), + markdownDescription: nls.localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass `#terminal.integrated.commandsToSkipShell#`, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code)."), type: 'boolean', default: true }, diff --git a/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts b/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts deleted file mode 100644 index 0430a52789..0000000000 --- a/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts +++ /dev/null @@ -1,252 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; -import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IEditorInputFactory, EditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, EditorModel, EditorOptions, GroupIdentifier, ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorModel } from 'vs/platform/editor/common/editor'; -import { Dimension, addDisposableListener, EventType } from 'vs/base/browser/dom'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { URI } from 'vs/base/common/uri'; -import { isEqual } from 'vs/base/common/resources'; -import { generateUuid } from 'vs/base/common/uuid'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IWorkingCopy, IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { env } from 'vs/base/common/process'; - -const CUSTOM_SCHEME = 'testCustomEditor'; -const ENABLE = !!env['VSCODE_DEV']; - -class TestCustomEditorsAction extends Action { - - static readonly ID = 'workbench.action.openCustomEditor'; - static readonly LABEL = nls.localize('openCustomEditor', "Test Open Custom Editor"); - - constructor( - id: string, - label: string, - @IEditorService private readonly editorService: IEditorService, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(id, label); - } - - async run(): Promise { - const input = this.instantiationService.createInstance(TestCustomEditorInput, URI.parse(`${CUSTOM_SCHEME}:/${generateUuid()}`)); - await this.editorService.openEditor(input); - - return true; - } -} - -class TestCustomEditor extends BaseEditor { - - static ID = 'testCustomEditor'; - - private textArea: HTMLTextAreaElement | undefined = undefined; - - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IThemeService themeService: IThemeService, - @IStorageService storageService: IStorageService - ) { - super(TestCustomEditor.ID, telemetryService, themeService, storageService); - } - - updateStyles(): void { - super.updateStyles(); - - if (this.textArea) { - this.textArea.style.backgroundColor = this.getColor(editorBackground)!.toString(); - this.textArea.style.color = this.getColor(editorForeground)!.toString(); - } - } - - protected createEditor(parent: HTMLElement): void { - this.textArea = document.createElement('textarea'); - this.textArea.style.width = '100%'; - this.textArea.style.height = '100%'; - - parent.appendChild(this.textArea); - - addDisposableListener(this.textArea, EventType.CHANGE, e => this.onDidType()); - addDisposableListener(this.textArea, EventType.KEY_UP, e => this.onDidType()); - - this.updateStyles(); - } - - private onDidType(): void { - if (this._input instanceof TestCustomEditorInput) { - this._input.setValue(this.textArea!.value); - } - } - - async setInput(input: EditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { - await super.setInput(input, options, token); - - const model = await input.resolve(); - if (model instanceof TestCustomEditorModel) { - this.textArea!.value = model.value; - } - } - - clearInput() { - super.clearInput(); - - this.textArea!.value = ''; - } - - focus(): void { - this.textArea!.focus(); - } - - layout(dimension: Dimension): void { } -} - -class TestCustomEditorInput extends EditorInput implements IWorkingCopy { - private model: TestCustomEditorModel | undefined = undefined; - - private dirty = false; - - readonly capabilities = 0; - - constructor(public readonly resource: URI, @IWorkingCopyService workingCopyService: IWorkingCopyService) { - super(); - - this._register(workingCopyService.registerWorkingCopy(this)); - } - - getResource(): URI { - return this.resource; - } - - getTypeId(): string { - return TestCustomEditor.ID; - } - - getName(): string { - return this.resource.toString(); - } - - setValue(value: string) { - if (this.model) { - if (this.model.value === value) { - return; - } - - this.model.value = value; - } - - this.setDirty(value.length > 0); - } - - private setDirty(dirty: boolean) { - if (this.dirty !== dirty) { - this.dirty = dirty; - this._onDidChangeDirty.fire(); - } - } - - isReadonly(): boolean { - return false; - } - - isDirty(): boolean { - return this.dirty; - } - - async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - this.setDirty(false); - - return true; - } - - async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - this.setDirty(false); - - return true; - } - - async revert(options?: IRevertOptions): Promise { - this.setDirty(false); - - return true; - } - - async resolve(): Promise { - if (!this.model) { - this.model = new TestCustomEditorModel(this.resource); - } - - return this.model; - } - - matches(other: EditorInput) { - return other instanceof TestCustomEditorInput && isEqual(other.resource, this.resource); - } - - dispose(): void { - this.setDirty(false); - - if (this.model) { - this.model.dispose(); - this.model = undefined; - } - - super.dispose(); - } -} - -class TestCustomEditorModel extends EditorModel { - - public value: string = ''; - - constructor(public readonly resource: URI) { - super(); - } -} - -if (ENABLE) { - Registry.as(EditorExtensions.Editors).registerEditor( - EditorDescriptor.create( - TestCustomEditor, - TestCustomEditor.ID, - nls.localize('testCustomEditor', "Test Custom Editor") - ), - [ - new SyncDescriptor(TestCustomEditorInput), - ] - ); - - const registry = Registry.as(Extensions.WorkbenchActions); - - registry.registerWorkbenchAction(SyncActionDescriptor.create(TestCustomEditorsAction, TestCustomEditorsAction.ID, TestCustomEditorsAction.LABEL), 'Test Open Custom Editor'); - - class TestCustomEditorInputFactory implements IEditorInputFactory { - - serialize(editorInput: TestCustomEditorInput): string { - return JSON.stringify({ - resource: editorInput.resource.toString() - }); - } - - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): TestCustomEditorInput { - return instantiationService.createInstance(TestCustomEditorInput, URI.parse(JSON.parse(serializedEditorInput).resource)); - } - } - - Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(TestCustomEditor.ID, TestCustomEditorInputFactory); -} diff --git a/src/vs/workbench/services/configuration/browser/configuration.ts b/src/vs/workbench/services/configuration/browser/configuration.ts index 9c0dff877a..0b0e7f35aa 100644 --- a/src/vs/workbench/services/configuration/browser/configuration.ts +++ b/src/vs/workbench/services/configuration/browser/configuration.ts @@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { FileChangeType, FileChangesEvent, IFileService } from 'vs/platform/files/common/files'; +import { FileChangeType, FileChangesEvent, IFileService, whenProviderRegistered } from 'vs/platform/files/common/files'; import { ConfigurationModel, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY, IConfigurationCache, ConfigurationKey, REMOTE_MACHINE_SCOPES, FOLDER_SCOPES, WORKSPACE_SCOPES } from 'vs/workbench/services/configuration/common/configuration'; @@ -24,20 +24,6 @@ import { IConfigurationModel } from 'vs/platform/configuration/common/configurat import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { hash } from 'vs/base/common/hash'; -function whenProviderRegistered(scheme: string, fileService: IFileService): Promise { - if (fileService.canHandleResource(URI.from({ scheme }))) { - return Promise.resolve(); - } - return new Promise((c, e) => { - const disposable = fileService.onDidChangeFileSystemProviderRegistrations(e => { - if (e.scheme === scheme && e.added) { - disposable.dispose(); - c(); - } - }); - }); -} - export class UserConfiguration extends Disposable { private readonly parser: ConfigurationModelParser; @@ -353,7 +339,7 @@ export class WorkspaceConfiguration extends Disposable { } private async waitAndSwitch(workspaceIdentifier: IWorkspaceIdentifier): Promise { - await whenProviderRegistered(workspaceIdentifier.configPath.scheme, this._fileService); + await whenProviderRegistered(workspaceIdentifier.configPath, this._fileService); if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) { const fileServiceBasedWorkspaceConfiguration = this._register(new FileServiceBasedWorkspaceConfiguration(this._fileService)); await fileServiceBasedWorkspaceConfiguration.load(workspaceIdentifier); @@ -750,7 +736,7 @@ export class FolderConfiguration extends Disposable implements IFolderConfigurat if (workspaceFolder.uri.scheme === Schemas.file) { this.folderConfiguration = new FileServiceBasedFolderConfiguration(this.configurationFolder, this.workbenchState, fileService); } else { - whenProviderRegistered(workspaceFolder.uri.scheme, fileService) + whenProviderRegistered(workspaceFolder.uri, fileService) .then(() => { this.folderConfiguration.dispose(); this.folderConfigurationDisposable.dispose(); diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 741f113bc1..4a4f8365de 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -338,7 +338,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } const resolvedKeybindings = this.resolveKeybinding(keybinding); - for (const resolvedKeybinding of resolvedKeybindings) { + for (let i = resolvedKeybindings.length - 1; i >= 0; i--) { + const resolvedKeybinding = resolvedKeybindings[i]; result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault); } } diff --git a/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts b/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts index 0d822bac28..13597f866d 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/nativeKeymapService.ts @@ -61,7 +61,7 @@ export class KeyboardMapperFactory { private static _isUSStandard(_kbInfo: nativeKeymap.IKeyboardLayoutInfo): boolean { if (OS === OperatingSystem.Linux) { const kbInfo = _kbInfo; - return (kbInfo && kbInfo.layout === 'us'); + return (kbInfo && (kbInfo.layout === 'us' || /^us,/.test(kbInfo.layout))); } if (OS === OperatingSystem.Macintosh) { diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 0f53ec4bb9..b493078787 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -1056,7 +1056,7 @@ export function createValidator(prop: IConfigurationPropertySchema): (value: any } if (prop.maxItems && stringArrayValue.length > prop.maxItems) { - message += nls.localize('validations.stringArrayMaxItem', 'Array must have less than {0} items', prop.maxItems); + message += nls.localize('validations.stringArrayMaxItem', 'Array must have at most {0} items', prop.maxItems); message += '\n'; } diff --git a/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts b/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts index 4b5d9ed273..3d16cafb9d 100644 --- a/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts +++ b/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts @@ -291,7 +291,7 @@ suite('Preferences Model test', () => { arr.rejects([]).withMessage('Array must have at least 1 items'); arr.accepts(['a']); arr.accepts(['a', 'a']); - arr.rejects(['a', 'a', 'a']).withMessage('Array must have less than 2 items'); + arr.rejects(['a', 'a', 'a']).withMessage('Array must have at most 2 items'); } }); @@ -312,7 +312,7 @@ suite('Preferences Model test', () => { test('min-max and enum', () => { const arr = new ArrayTester({ type: 'array', items: { type: 'string', enum: ['a', 'b'] }, minItems: 1, maxItems: 2 }); - arr.rejects(['a', 'b', 'c']).withMessage('Array must have less than 2 items'); + arr.rejects(['a', 'b', 'c']).withMessage('Array must have at most 2 items'); arr.rejects(['a', 'b', 'c']).withMessage(`Value 'c' is not one of`); }); diff --git a/src/vs/workbench/services/search/common/searchService.ts b/src/vs/workbench/services/search/common/searchService.ts index 78c297c635..b38e6e8753 100644 --- a/src/vs/workbench/services/search/common/searchService.ts +++ b/src/vs/workbench/services/search/common/searchService.ts @@ -409,6 +409,11 @@ export class SearchService extends Disposable implements ISearchService { return; } + // Exclude files from the git FileSystemProvider, e.g. to prevent open staged files from showing in search results + if (resource.scheme === 'gitfs') { + return; + } + if (!this.matches(resource, query)) { return; // respect user filters } diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index 4fa52a8681..3e86a16f7b 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -350,8 +350,9 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { // Global settings defaultEncodingOverrides.push({ parent: this.environmentService.userRoamingDataHome, encoding: UTF8 }); - // Workspace files + // Workspace files (via extension and via untitled workspaces location) defaultEncodingOverrides.push({ extension: WORKSPACE_EXTENSION, encoding: UTF8 }); + defaultEncodingOverrides.push({ parent: this.environmentService.untitledWorkspacesHome, encoding: UTF8 }); // Folder Settings this.contextService.getWorkspace().folders.forEach(folder => { diff --git a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts index 7bb85407f0..7a80489781 100644 --- a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts +++ b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts @@ -6,7 +6,7 @@ import { ColorThemeData } from 'vs/workbench/services/themes/common/colorThemeData'; import * as assert from 'assert'; import { ITokenColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { TokenStyle, comments, variables, types, functions, keywords, numbers, strings, getTokenClassificationRegistry } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { TokenStyle, getTokenClassificationRegistry } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { Color } from 'vs/base/common/color'; import { isString } from 'vs/base/common/types'; import { FileService } from 'vs/platform/files/common/fileService'; @@ -107,13 +107,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#88846f', undefinedStyle), - [variables]: ts('#F8F8F2', unsetStyle), - [types]: ts('#A6E22E', { underline: true }), - [functions]: ts('#A6E22E', unsetStyle), - [strings]: ts('#E6DB74', undefinedStyle), - [numbers]: ts('#AE81FF', undefinedStyle), - [keywords]: ts('#F92672', undefinedStyle) + 'comment': ts('#88846f', undefinedStyle), + 'variable': ts('#F8F8F2', unsetStyle), + 'type': ts('#A6E22E', { underline: true }), + 'function': ts('#A6E22E', unsetStyle), + 'string': ts('#E6DB74', undefinedStyle), + 'number': ts('#AE81FF', undefinedStyle), + 'keyword': ts('#F92672', undefinedStyle) }); }); @@ -127,13 +127,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#6A9955', undefinedStyle), - [variables]: ts('#9CDCFE', undefinedStyle), - [types]: ts('#4EC9B0', undefinedStyle), - [functions]: ts('#DCDCAA', undefinedStyle), - [strings]: ts('#CE9178', undefinedStyle), - [numbers]: ts('#B5CEA8', undefinedStyle), - [keywords]: ts('#C586C0', undefinedStyle) + 'comment': ts('#6A9955', undefinedStyle), + 'variable': ts('#9CDCFE', undefinedStyle), + 'type': ts('#4EC9B0', undefinedStyle), + 'function': ts('#DCDCAA', undefinedStyle), + 'string': ts('#CE9178', undefinedStyle), + 'number': ts('#B5CEA8', undefinedStyle), + 'keyword': ts('#C586C0', undefinedStyle) }); }); @@ -147,13 +147,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#008000', undefinedStyle), - [variables]: ts(undefined, undefinedStyle), - [types]: ts(undefined, undefinedStyle), - [functions]: ts(undefined, undefinedStyle), - [strings]: ts('#a31515', undefinedStyle), - [numbers]: ts('#09885a', undefinedStyle), - [keywords]: ts('#0000ff', undefinedStyle) + 'comment': ts('#008000', undefinedStyle), + 'variable': ts(undefined, undefinedStyle), + 'type': ts(undefined, undefinedStyle), + 'function': ts(undefined, undefinedStyle), + 'string': ts('#a31515', undefinedStyle), + 'number': ts('#09885a', undefinedStyle), + 'keyword': ts('#0000ff', undefinedStyle) }); }); @@ -167,13 +167,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#7ca668', undefinedStyle), - [variables]: ts('#9CDCFE', undefinedStyle), - [types]: ts('#4EC9B0', undefinedStyle), - [functions]: ts('#DCDCAA', undefinedStyle), - [strings]: ts('#ce9178', undefinedStyle), - [numbers]: ts('#b5cea8', undefinedStyle), - [keywords]: ts('#C586C0', undefinedStyle) + 'comment': ts('#7ca668', undefinedStyle), + 'variable': ts('#9CDCFE', undefinedStyle), + 'type': ts('#4EC9B0', undefinedStyle), + 'function': ts('#DCDCAA', undefinedStyle), + 'string': ts('#ce9178', undefinedStyle), + 'number': ts('#b5cea8', undefinedStyle), + 'keyword': ts('#C586C0', undefinedStyle) }); }); @@ -187,13 +187,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#a57a4c', undefinedStyle), - [variables]: ts('#dc3958', undefinedStyle), - [types]: ts('#f06431', undefinedStyle), - [functions]: ts('#8ab1b0', undefinedStyle), - [strings]: ts('#889b4a', undefinedStyle), - [numbers]: ts('#f79a32', undefinedStyle), - [keywords]: ts('#98676a', undefinedStyle) + 'comment': ts('#a57a4c', undefinedStyle), + 'variable': ts('#dc3958', undefinedStyle), + 'type': ts('#f06431', undefinedStyle), + 'function': ts('#8ab1b0', undefinedStyle), + 'string': ts('#889b4a', undefinedStyle), + 'number': ts('#f79a32', undefinedStyle), + 'keyword': ts('#98676a', undefinedStyle) }); }); @@ -207,13 +207,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#384887', undefinedStyle), - [variables]: ts(undefined, unsetStyle), - [types]: ts('#ffeebb', { underline: true }), - [functions]: ts('#ddbb88', unsetStyle), - [strings]: ts('#22aa44', undefinedStyle), - [numbers]: ts('#f280d0', undefinedStyle), - [keywords]: ts('#225588', undefinedStyle) + 'comment': ts('#384887', undefinedStyle), + 'variable': ts(undefined, unsetStyle), + 'type': ts('#ffeebb', { underline: true }), + 'function': ts('#ddbb88', unsetStyle), + 'string': ts('#22aa44', undefinedStyle), + 'number': ts('#f280d0', undefinedStyle), + 'keyword': ts('#225588', undefinedStyle) }); }); @@ -301,8 +301,8 @@ suite('Themes - TokenStyleResolving', () => { const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); themeData.setCustomColors({ 'editor.foreground': '#000000' }); themeData.setCustomTokenStyleRules({ - 'types': '#ff0000', - 'classes': { foreground: '#0000ff', fontStyle: 'italic' }, + 'type': '#ff0000', + 'class': { foreground: '#0000ff', fontStyle: 'italic' }, '*.static': { fontStyle: 'bold' }, '*.declaration': { fontStyle: 'italic' }, '*.async.static': { fontStyle: 'italic underline' }, @@ -310,14 +310,14 @@ suite('Themes - TokenStyleResolving', () => { }); assertTokenStyles(themeData, { - 'types': ts('#ff0000', undefinedStyle), - 'types.static': ts('#ff0000', { bold: true }), - 'types.static.declaration': ts('#ff0000', { bold: true, italic: true }), - 'classes': ts('#0000ff', { italic: true }), - 'classes.static.declaration': ts('#0000ff', { bold: true, italic: true }), - 'classes.declaration': ts('#0000ff', { italic: true }), - 'classes.declaration.async': ts('#000fff', { underline: true, italic: false }), - 'classes.declaration.async.static': ts('#000fff', { italic: true, underline: true, bold: true }), + 'type': ts('#ff0000', undefinedStyle), + 'type.static': ts('#ff0000', { bold: true }), + 'type.static.declaration': ts('#ff0000', { bold: true, italic: true }), + 'class': ts('#0000ff', { italic: true }), + 'class.static.declaration': ts('#0000ff', { bold: true, italic: true }), + 'class.declaration': ts('#0000ff', { italic: true }), + 'class.declaration.async': ts('#000fff', { underline: true, italic: false }), + 'class.declaration.async.static': ts('#000fff', { italic: true, underline: true, bold: true }), }); }); diff --git a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts index f7427e9896..4d936be51f 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts @@ -10,6 +10,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { URI } from 'vs/base/common/uri'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ITextResourcePropertiesService, ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; class UserDataSyncUtilService implements IUserDataSyncUtilService { @@ -18,6 +19,8 @@ class UserDataSyncUtilService implements IUserDataSyncUtilService { constructor( @IKeybindingService private readonly keybindingsService: IKeybindingService, @ITextModelService private readonly textModelService: ITextModelService, + @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService, + @ITextResourceConfigurationService private readonly textResourceConfigurationService: ITextResourceConfigurationService, ) { } public async resolveUserBindings(userBindings: string[]): Promise> { @@ -29,11 +32,19 @@ class UserDataSyncUtilService implements IUserDataSyncUtilService { } async resolveFormattingOptions(resource: URI): Promise { - const modelReference = await this.textModelService.createModelReference(resource); - const { insertSpaces, tabSize } = modelReference.object.textEditorModel.getOptions(); - const eol = modelReference.object.textEditorModel.getEOL(); - modelReference.dispose(); - return { eol, insertSpaces, tabSize }; + try { + const modelReference = await this.textModelService.createModelReference(resource); + const { insertSpaces, tabSize } = modelReference.object.textEditorModel.getOptions(); + const eol = modelReference.object.textEditorModel.getEOL(); + modelReference.dispose(); + return { eol, insertSpaces, tabSize }; + } catch (e) { + } + return { + eol: this.textResourcePropertiesService.getEOL(resource), + insertSpaces: this.textResourceConfigurationService.getValue(resource, 'editor.insertSpaces'), + tabSize: this.textResourceConfigurationService.getValue(resource, 'editor.tabSize') + }; } } diff --git a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts index 58706c9fd5..c40255e2df 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts @@ -120,7 +120,7 @@ suite('ExtHostWorkspace', function () { assert.equal(ws.getPath(), undefined); ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('Folder'), 0), aWorkspaceFolderData(URI.file('Another/Folder'), 1)] }, new NullLogService()); - assert.equal(ws.getPath()!.replace(/\\/g, '/'), '/Folder'); + assert.equal(ws.getPath(), undefined); ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('/Folder'), 0)] }, new NullLogService()); assert.equal(ws.getPath()!.replace(/\\/g, '/'), '/Folder'); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index b690871dd0..9049477fd9 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -364,9 +364,6 @@ import 'vs/workbench/contrib/userDataSync/browser/userDataSync.contribution'; // Code Actions import 'vs/workbench/contrib/codeActions/common/codeActions.contribution'; -// Test Custom Editors -import 'vs/workbench/contrib/testCustomEditors/browser/testCustomEditors'; - //#endregion //#region -- contributions diff --git a/test/automation/src/debug.ts b/test/automation/src/debug.ts index 9c9de81d6e..5992846dbd 100644 --- a/test/automation/src/debug.ts +++ b/test/automation/src/debug.ts @@ -11,7 +11,7 @@ import { Editor } from './editor'; import { IElement } from '../src/driver'; const VIEWLET = 'div[id="workbench.view.debug"]'; -const DEBUG_VIEW = `${VIEWLET} .debug-view-content`; +const DEBUG_VIEW = `${VIEWLET}`; const CONFIGURE = `div[id="workbench.parts.sidebar"] .actions-container .codicon-gear`; const STOP = `.debug-toolbar .action-label[title*="Stop"]`; const STEP_OVER = `.debug-toolbar .action-label[title*="Step Over"]`; diff --git a/test/smoke/src/areas/debug/debug.test.ts b/test/smoke/src/areas/debug/debug.test.ts index 3c37670b74..a61d02745d 100644 --- a/test/smoke/src/areas/debug/debug.test.ts +++ b/test/smoke/src/areas/debug/debug.test.ts @@ -17,7 +17,7 @@ export function setup() { await app.workbench.debug.openDebugViewlet(); await app.workbench.quickopen.openFile('app.js'); - await app.workbench.debug.configure(); + await app.workbench.quickopen.runCommand('Debug: Open launch.json'); const launchJsonPath = path.join(app.workspacePathOrFolder, '.vscode', 'launch.json'); const content = fs.readFileSync(launchJsonPath, 'utf8'); diff --git a/yarn.lock b/yarn.lock index aeecd02713..5b3f26dae6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9673,10 +9673,10 @@ xterm-addon-webgl@0.4.0-beta.11: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.4.0-beta.11.tgz#0e4a7242e2353cf74aba55e5a5bdc0b4ec87ad10" integrity sha512-AteDxm1RFy1WnjY9r5iJSETozLebvUkR+jextdZk/ASsK21vsYK0DuVWwRI8afgiN2hUVhxcxuHEJUOV+CJDQA== -xterm@4.3.0-beta.28: - version "4.3.0-beta.28" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.tgz#80f7c4ba8f6ee3c953e6f33f8ce5aef08d5a8354" - integrity sha512-WWZ4XCvce5h+klL6ObwtMauJff/n2KGGOwJJkDbJhrAjVy2a77GKgAedJTDDFGgKJ6ix1d7puHtVSSKflIVaDQ== +xterm@4.3.0-beta.28.vscode.1: + version "4.3.0-beta.28.vscode.1" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.3.0-beta.28.vscode.1.tgz#89b85398b5801708e833d08bf92b2124fa128943" + integrity sha512-JNHNZyDtAWnybJTrenPzD6g/yXpHOvPqmjau91Up4onRbjQYMSNlth17SqaES68DKn/+4kcIl2c/RG5SXJjvGw== y18n@^3.2.1: version "3.2.1"