diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index 7f439dd686a..593ecdff80a 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -9,7 +9,7 @@ }, "main": "./out/cssServerMain", "dependencies": { - "vscode-css-languageservice": "^4.1.0", + "vscode-css-languageservice": "^4.1.1", "vscode-languageserver": "^6.1.1" }, "devDependencies": { diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index e57ff40f62e..902123900d0 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -689,10 +689,10 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -vscode-css-languageservice@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.1.0.tgz#144c8274e0bf1719fa6f773ca684bd1c7ffd634f" - integrity sha512-iTX3dTp0Y0RFWhIux5jasI8r9swdiWVB1Z3OrZ10iDHxzkETjVPxAQ5BEQU4ag0Awc8TTg1C7sJriHQY2LO14g== +vscode-css-languageservice@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-4.1.1.tgz#9131dd465e4b20f3ba78ab9734b2c7cdb9237443" + integrity sha512-2r2bYbhscivRu1zqh5kNe8aYpFnfksMYC7wTpKX2HqFsSzSJYXk0sCqPaWsP5ptqz0OFBTXnzx2JlE+Nb5Edgw== dependencies: vscode-languageserver-textdocument "^1.0.1" vscode-languageserver-types "^3.15.1" diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index fc43a136a01..38f6f5231b5 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -27,8 +27,8 @@ export class PreviewManager implements vscode.CustomEditorProvider { private readonly zoomStatusBarEntry: ZoomStatusBarEntry, ) { } - public async resolveCustomDocument(_document: vscode.CustomDocument): Promise { - return {}; + public async resolveCustomDocument(_document: vscode.CustomDocument): Promise { + // noop } public async resolveCustomEditor( diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts index a6fc8ec528c..a9403313931 100644 --- a/extensions/markdown-language-features/src/features/previewManager.ts +++ b/extensions/markdown-language-features/src/features/previewManager.ts @@ -148,8 +148,8 @@ export class MarkdownPreviewManager extends Disposable implements vscode.Webview this.registerDynamicPreview(preview); } - public async resolveCustomDocument(_document: vscode.CustomDocument): Promise { - return {}; + public async resolveCustomDocument(_document: vscode.CustomDocument): Promise { + // noop } public async resolveCustomTextEditor( diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index b3f12439bc8..e803011bfb4 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -19,7 +19,7 @@ "jsonc-parser": "^2.2.1", "rimraf": "^2.6.3", "semver": "5.5.1", - "typescript-vscode-sh-plugin": "^0.6.8", + "typescript-vscode-sh-plugin": "^0.6.10", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.1.1" }, diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 82daf1829dd..1934b8a6810 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -626,10 +626,10 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -typescript-vscode-sh-plugin@^0.6.8: - version "0.6.8" - resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.6.8.tgz#60d5025f2ab814496824ee997b5e9fc12c5b7f1a" - integrity sha512-XEh/GwBRsZKWQjPTODqWWiW8o8DyF7Yzfp/xvq1vyK5Z9JykFKAkx95BEmALv9x9dpc2RcLZHgVsKFXrtDABCw== +typescript-vscode-sh-plugin@^0.6.10: + version "0.6.10" + resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.6.10.tgz#f9fdac506a3adb698d52fd01723ec78e8a5fc09e" + integrity sha512-cYycpwLnYT2oS48tac+UvVRtIFHHTcHAz/g3N2HpYftuMEBvBcsGfe2SrlnrGCa1gMheTbo+twIHhsQu9ygdvg== uri-js@^4.2.2: version "4.2.2" diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index 8ac6b2806ca..c6fec9f7fb2 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -10,7 +10,7 @@ "onFileSystem:memfs", "onDebug" ], - "main": "./out/extension", + "main": "./out/web-playground/extension", "engines": { "vscode": "^1.25.0" }, diff --git a/extensions/vscode-api-tests/src/extension.ts b/extensions/vscode-api-tests/src/web-playground/extension.ts similarity index 100% rename from extensions/vscode-api-tests/src/extension.ts rename to extensions/vscode-api-tests/src/web-playground/extension.ts diff --git a/package.json b/package.json index 6fc79f7aba1..dcc6b7a6341 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.44.0", - "distro": "07db0bb3dc2da82ce33f2871e0093df1b9085d06", + "distro": "de617fbc2d2b5e151b9e4f1713fcd5d29dda04ea", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index bdc79116318..0108b531460 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -85,6 +85,7 @@ const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; * (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation * and encoding. * + * ```txt * foo://example.com:8042/over/there?name=ferret#nose * \_/ \______________/\_________/ \_________/ \__/ * | | | | | @@ -92,6 +93,7 @@ const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; * | _____________________|__ * / \ / \ * urn:example:animal:ferret:nose + * ``` */ export class URI implements UriComponents { diff --git a/src/vs/editor/common/standaloneStrings.ts b/src/vs/editor/common/standaloneStrings.ts index 872aa8aafb4..8ca766d7240 100644 --- a/src/vs/editor/common/standaloneStrings.ts +++ b/src/vs/editor/common/standaloneStrings.ts @@ -61,6 +61,7 @@ export namespace QuickOutlineNLS { export const entryAriaLabel = nls.localize('entryAriaLabel', "{0}, symbols"); export const quickOutlineActionInput = nls.localize('quickOutlineActionInput', "Type the name of an identifier you wish to navigate to"); export const quickOutlineActionLabel = nls.localize('quickOutlineActionLabel', "Go to Symbol..."); + export const quickOutlineByCategoryActionLabel = nls.localize('quickOutlineByCategoryActionLabel', "Go to Symbol by Category..."); export const _symbols_ = nls.localize('symbols', "symbols ({0})"); export const _modules_ = nls.localize('modules', "modules ({0})"); export const _class_ = nls.localize('class', "classes ({0})"); diff --git a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts index bc2cf02ef59..06847ad5d62 100644 --- a/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts +++ b/src/vs/editor/contrib/quickAccess/gotoLineQuickAccess.ts @@ -53,7 +53,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor } private doProvideWithoutTextEditor(picker: IQuickPick): IDisposable { - const label = localize('cannotRunGotoLine', "Open a text file first to go to a line."); + const label = localize('cannotRunGotoLine', "Open a text editor first to go to a line."); picker.items = [{ label }]; picker.ariaLabel = label; diff --git a/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts new file mode 100644 index 00000000000..1c66d349e0a --- /dev/null +++ b/src/vs/editor/contrib/quickAccess/gotoSymbolQuickAccess.ts @@ -0,0 +1,390 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IQuickPick, IQuickPickItem, IKeyMods, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { DisposableStore, toDisposable, IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { once } from 'vs/base/common/functional'; +import { IEditor, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; +import { ITextModel } from 'vs/editor/common/model'; +import { isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { AbstractEditorQuickAccessProvider } from 'vs/editor/contrib/quickAccess/quickAccess'; +import { DocumentSymbol, SymbolKinds, SymbolTag, DocumentSymbolProviderRegistry, SymbolKind } from 'vs/editor/common/modes'; +import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { values } from 'vs/base/common/collections'; +import { trim, format } from 'vs/base/common/strings'; +import { fuzzyScore, FuzzyScore, createMatches } from 'vs/base/common/filters'; + +interface IGotoSymbolQuickPickItem extends IQuickPickItem { + kind: SymbolKind, + index: number, + score?: FuzzyScore; + range?: { decoration: IRange, selection: IRange }, +} + +export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorQuickAccessProvider { + + static PREFIX = '@'; + static SCOPE_PREFIX = ':'; + static PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`; + + provide(picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Disable filtering & sorting, we control the results + picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false; + + // Provide based on current active editor + let pickerDisposable = this.doProvide(picker, token); + disposables.add(toDisposable(() => pickerDisposable.dispose())); + + // Re-create whenever the active editor changes + disposables.add(this.onDidActiveTextEditorControlChange(() => { + pickerDisposable.dispose(); + pickerDisposable = this.doProvide(picker, token); + })); + + return disposables; + } + + private doProvide(picker: IQuickPick, token: CancellationToken): IDisposable { + const activeTextEditorControl = this.activeTextEditorControl; + + // With text control + if (activeTextEditorControl) { + const model = this.getModel(activeTextEditorControl); + if (model && DocumentSymbolProviderRegistry.has(model)) { + return this.doProvideWithSymbols(activeTextEditorControl, model, picker, token); + } + } + + // Without text control + return this.doProvideWithoutSymbols(picker); + } + + private doProvideWithoutSymbols(picker: IQuickPick): IDisposable { + const label = localize('cannotRunGotoSymbol', "Open a text editor with symbol information first to go to a symbol."); + picker.items = [{ label, index: 0, kind: SymbolKind.String }]; + picker.ariaLabel = label; + + return Disposable.None; + } + + private doProvideWithSymbols(editor: IEditor, model: ITextModel, picker: IQuickPick, token: CancellationToken): IDisposable { + const disposables = new DisposableStore(); + + // Restore any view state if this picker was closed + // without actually going to a symbol + const lastKnownEditorViewState = withNullAsUndefined(editor.saveViewState()); + once(token.onCancellationRequested)(() => { + if (lastKnownEditorViewState) { + editor.restoreViewState(lastKnownEditorViewState); + } + }); + + // Goto symbol once picked + disposables.add(picker.onDidAccept(() => { + const [item] = picker.selectedItems; + if (item && item.range) { + this.gotoSymbol(editor, item.range.selection, picker.keyMods); + + picker.hide(); + } + })); + + // Set initial picks and update on type + let picksCts: CancellationTokenSource | undefined = undefined; + const updatePickerItems = async () => { + + // Cancel any previous ask for picks and busy + picksCts?.dispose(true); + picker.busy = false; + + // Create new cancellation source for this run + picksCts = new CancellationTokenSource(token); + + // Collect symbol picks + picker.busy = true; + try { + const items = await this.getSymbolPicks(model, picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim(), picksCts.token); + if (token.isCancellationRequested) { + return; + } + + picker.items = items; + } finally { + if (!token.isCancellationRequested) { + picker.busy = false; + } + } + }; + disposables.add(picker.onDidChangeValue(() => updatePickerItems())); + updatePickerItems(); + + // Reveal and decorate when active item changes + disposables.add(picker.onDidChangeActive(() => { + const [item] = picker.activeItems; + if (item && item.range) { + + // Reveal + editor.revealRangeInCenter(item.range.selection, ScrollType.Smooth); + + // Decorate + this.addDecorations(editor, item.range.decoration); + } + })); + + // Clean up decorations on dispose + disposables.add(toDisposable(() => this.clearDecorations(editor))); + + return disposables; + } + + private async getSymbolPicks(model: ITextModel, filter: string, token: CancellationToken): Promise> { + + // Resolve symbols from document + const symbols = await this.getDocumentSymbols(model, true, token); + if (token.isCancellationRequested) { + return []; + } + + // Normalize filter + const filterBySymbolKind = filter.indexOf(AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX) === 0; + const filterLow = filter.toLowerCase(); + const filterPos = filterBySymbolKind ? 1 : 0; + + // Convert to symbol picks and apply filtering + const filteredSymbolPicks: IGotoSymbolQuickPickItem[] = []; + for (let index = 0; index < symbols.length; index++) { + const symbol = symbols[index]; + + const label = trim(symbol.name); + const deprecated = symbol.tags && symbol.tags.indexOf(SymbolTag.Deprecated) >= 0; + + let score: FuzzyScore | undefined = undefined; + let includeSymbol = true; + if (filter.length > filterPos) { + score = fuzzyScore(filter, filterLow, filterPos, label, label.toLowerCase(), 0, true); + includeSymbol = !!score; + } + + if (includeSymbol) { + const labelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${label}`; + + // Readjust matches to account for codicons + const labelOffset = labelWithIcon.length - label.length; + const matches = createMatches(score); + if (matches) { + for (const match of matches) { + match.start += labelOffset; + match.end += labelOffset; + } + } + + filteredSymbolPicks.push({ + index, + kind: symbol.kind, + score, + label: labelWithIcon, + ariaLabel: localize('symbolsAriaLabel', "{0}, symbols picker", label), + description: symbol.containerName, + highlights: deprecated ? undefined : { label: matches }, + range: { + selection: Range.collapseToStart(symbol.selectionRange), + decoration: symbol.range + }, + italic: deprecated + }); + } + } + + // Sort by score + const sortedFilteredSymbolPicks = filteredSymbolPicks.sort((symbolA, symbolB) => filterBySymbolKind ? + this.compareByKindAndScore(symbolA, symbolB) : + this.compareByScore(symbolA, symbolB) + ); + + // Add separator for types + // - @ only total number of symbols + // - @: grouped by symbol kind + let symbolPicks: Array = []; + if (filterBySymbolKind) { + let lastSymbolKind: SymbolKind | undefined = undefined; + let lastSeparator: IQuickPickSeparator | undefined = undefined; + let lastSymbolKindCounter = 0; + + function updateLastSeparatorLabel(): void { + if (lastSeparator && typeof lastSymbolKind === 'number' && lastSymbolKindCounter > 0) { + lastSeparator.label = format(NLS_SYMBOL_KIND_CACHE[lastSymbolKind] || FALLBACK_NLS_SYMBOL_KIND, lastSymbolKindCounter); + } + } + + for (const symbolPick of sortedFilteredSymbolPicks) { + + // Found new kind + if (lastSymbolKind !== symbolPick.kind) { + + // Update last separator with number of symbols we found for kind + updateLastSeparatorLabel(); + + lastSymbolKind = symbolPick.kind; + lastSymbolKindCounter = 1; + + // Add new separator for new kind + lastSeparator = { type: 'separator' }; + symbolPicks.push(lastSeparator); + } + + // Existing kind, keep counting + else { + lastSymbolKindCounter++; + } + + // Add to final result + symbolPicks.push(symbolPick); + } + + // Update last separator with number of symbols we found for kind + updateLastSeparatorLabel(); + } else { + symbolPicks = [ + { label: localize('symbols', "symbols ({0})", filteredSymbolPicks.length), type: 'separator' }, + ...sortedFilteredSymbolPicks + ]; + } + + return symbolPicks; + } + + private compareByScore(symbolA: IGotoSymbolQuickPickItem, symbolB: IGotoSymbolQuickPickItem): number { + if (!symbolA.score && symbolB.score) { + return 1; + } else if (symbolA.score && !symbolB.score) { + return -1; + } + + if (symbolA.score && symbolB.score) { + if (symbolA.score[0] > symbolB.score[0]) { + return -1; + } else if (symbolA.score[0] < symbolB.score[0]) { + return 1; + } + } + + if (symbolA.index < symbolB.index) { + return -1; + } else if (symbolA.index > symbolB.index) { + return 1; + } + + return 0; + } + + private compareByKindAndScore(symbolA: IGotoSymbolQuickPickItem, symbolB: IGotoSymbolQuickPickItem): number { + const kindA = NLS_SYMBOL_KIND_CACHE[symbolA.kind] || FALLBACK_NLS_SYMBOL_KIND; + const kindB = NLS_SYMBOL_KIND_CACHE[symbolB.kind] || FALLBACK_NLS_SYMBOL_KIND; + + // Sort by type first if scoped search + const result = kindA.localeCompare(kindB); + if (result === 0) { + return this.compareByScore(symbolA, symbolB); + } + + return result; + } + + private async getDocumentSymbols(document: ITextModel, flatten: boolean, token: CancellationToken): Promise { + const model = await OutlineModel.create(document, token); + if (token.isCancellationRequested) { + return []; + } + + const roots: DocumentSymbol[] = []; + for (const child of values(model.children)) { + if (child instanceof OutlineElement) { + roots.push(child.symbol); + } else { + roots.push(...values(child.children).map(child => child.symbol)); + } + } + + let flatEntries: DocumentSymbol[] = []; + if (flatten) { + this.flattenDocumentSymbols(flatEntries, roots, ''); + } else { + flatEntries = roots; + } + + return flatEntries.sort((symbolA, symbolB) => Range.compareRangesUsingStarts(symbolA.range, symbolB.range)); + } + + private flattenDocumentSymbols(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideContainerLabel: string): void { + for (const entry of entries) { + bucket.push({ + kind: entry.kind, + tags: entry.tags, + name: entry.name, + detail: entry.detail, + containerName: entry.containerName || overrideContainerLabel, + range: entry.range, + selectionRange: entry.selectionRange, + children: undefined, // we flatten it... + }); + + // Recurse over children + if (entry.children) { + this.flattenDocumentSymbols(bucket, entry.children, entry.name); + } + } + } + + private getModel(editor: IEditor | IDiffEditor): ITextModel | undefined { + return isDiffEditor(editor) ? + editor.getModel()?.modified : + editor.getModel() as ITextModel; + } + + protected gotoSymbol(editor: IEditor, range: IRange, keyMods: IKeyMods): void { + editor.setSelection(range); + editor.revealRangeInCenter(range, ScrollType.Smooth); + editor.focus(); + } +} + +// #region NLS Helpers + +const FALLBACK_NLS_SYMBOL_KIND = localize('property', "properties ({0})"); +const NLS_SYMBOL_KIND_CACHE: { [type: number]: string } = { + [SymbolKind.Method]: localize('method', "methods ({0})"), + [SymbolKind.Function]: localize('function', "functions ({0})"), + [SymbolKind.Constructor]: localize('_constructor', "constructors ({0})"), + [SymbolKind.Variable]: localize('variable', "variables ({0})"), + [SymbolKind.Class]: localize('class', "classes ({0})"), + [SymbolKind.Struct]: localize('struct', "structs ({0})"), + [SymbolKind.Event]: localize('event', "events ({0})"), + [SymbolKind.Operator]: localize('operator', "operators ({0})"), + [SymbolKind.Interface]: localize('interface', "interfaces ({0})"), + [SymbolKind.Namespace]: localize('namespace', "namespaces ({0})"), + [SymbolKind.Package]: localize('package', "packages ({0})"), + [SymbolKind.TypeParameter]: localize('typeParameter', "type parameters ({0})"), + [SymbolKind.Module]: localize('modules', "modules ({0})"), + [SymbolKind.Property]: localize('property', "properties ({0})"), + [SymbolKind.Enum]: localize('enum', "enumerations ({0})"), + [SymbolKind.EnumMember]: localize('enumMember', "enumeration members ({0})"), + [SymbolKind.String]: localize('string', "strings ({0})"), + [SymbolKind.File]: localize('file', "files ({0})"), + [SymbolKind.Array]: localize('array', "arrays ({0})"), + [SymbolKind.Number]: localize('number', "numbers ({0})"), + [SymbolKind.Boolean]: localize('boolean', "booleans ({0})"), + [SymbolKind.Object]: localize('object', "objects ({0})"), + [SymbolKind.Key]: localize('key', "keys ({0})"), + [SymbolKind.Field]: localize('field', "fields ({0})"), + [SymbolKind.Constant]: localize('constant', "constants ({0})") +}; + +//#endregion diff --git a/src/vs/editor/editor.main.ts b/src/vs/editor/editor.main.ts index 302ff27f8a2..6e3f581f70a 100644 --- a/src/vs/editor/editor.main.ts +++ b/src/vs/editor/editor.main.ts @@ -12,6 +12,7 @@ import 'vs/editor/standalone/browser/quickOpen/quickCommand'; import 'vs/editor/standalone/browser/quickOpen/quickOutline'; import 'vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess'; import 'vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess'; +import 'vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess'; import 'vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess'; import 'vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch'; import 'vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast'; diff --git a/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts new file mode 100644 index 00000000000..7be920e5129 --- /dev/null +++ b/src/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AbstractGotoSymbolQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { QuickOutlineNLS } from 'vs/editor/common/standaloneStrings'; +import { Event } from 'vs/base/common/event'; + +export class StandaloneGotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { + + readonly onDidActiveTextEditorControlChange = Event.None; + + constructor(@ICodeEditorService private readonly editorService: ICodeEditorService) { + super(); + } + + get activeTextEditorControl() { + return withNullAsUndefined(this.editorService.getFocusedCodeEditor()); + } +} + +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: StandaloneGotoSymbolQuickAccessProvider, + prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, + helpEntries: [ + { description: QuickOutlineNLS.quickOutlineActionLabel, prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, needsEditor: true }, + { description: QuickOutlineNLS.quickOutlineByCategoryActionLabel, prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true } + ] +}); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 8b4ab293ccc..08918576d0e 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -74,6 +74,7 @@ declare namespace monaco { * (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation * and encoding. * + * ```txt * foo://example.com:8042/over/there?name=ferret#nose * \_/ \______________/\_________/ \_________/ \__/ * | | | | | @@ -81,6 +82,7 @@ declare namespace monaco { * | _____________________|__ * / \ / \ * urn:example:animal:ferret:nose + * ``` */ export class Uri implements UriComponents { static isUri(thing: any): thing is Uri; diff --git a/src/vs/platform/opener/browser/link.ts b/src/vs/platform/opener/browser/link.ts index daf424fa848..7b6863cd048 100644 --- a/src/vs/platform/opener/browser/link.ts +++ b/src/vs/platform/opener/browser/link.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { $ } from 'vs/base/browser/dom'; +import { $, EventHelper, EventLike } from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -46,9 +46,12 @@ export class Link extends Disposable { .map(e => new StandardKeyboardEvent(e)) .filter(e => e.keyCode === KeyCode.Enter) .event; - const onOpen = Event.any(onClick, onEnterPress); + const onOpen = Event.any(onClick, onEnterPress); - this._register(onOpen(_ => openerService.open(link.href))); + this._register(onOpen(e => { + EventHelper.stop(e, true); + openerService.open(link.href); + })); this.applyStyles(); } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 6dbe9fb8999..c525746b88c 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1238,81 +1238,71 @@ declare module 'vscode' { // - Should we expose edits? // - More properties from `TextDocument`? - /** - * Defines the capabilities of a custom webview editor. - */ - interface CustomEditorCapabilities { - /** - * Defines the editing capability of a custom webview document. - * - * When not provided, the document is considered readonly. - */ - readonly editing?: CustomEditorEditingCapability; - } - /** * Defines the editing capability of a custom webview editor. This allows the webview editor to hook into standard * editor events such as `undo` or `save`. * * @param EditType Type of edits. */ - interface CustomEditorEditingCapability { + interface CustomEditorEditingDelegate { /** * Save the resource. * + * @param document Document to save. * @param cancellation Token that signals the save is no longer required (for example, if another save was triggered). * * @return Thenable signaling that the save has completed. */ - save(cancellation: CancellationToken): Thenable; + save(document: CustomDocument, cancellation: CancellationToken): Thenable; /** * Save the existing resource at a new path. * + * @param document Document to save. * @param targetResource Location to save to. * * @return Thenable signaling that the save has completed. */ - saveAs(targetResource: Uri): Thenable; + saveAs(document: CustomDocument, targetResource: Uri): Thenable; /** * Event triggered by extensions to signal to VS Code that an edit has occurred. */ - readonly onDidEdit: Event; + readonly onDidEdit: Event>; /** * Apply a set of edits. * * Note that is not invoked when `onDidEdit` is called because `onDidEdit` implies also updating the view to reflect the edit. * + * @param document Document to apply edits to. * @param edit Array of edits. Sorted from oldest to most recent. * * @return Thenable signaling that the change has completed. */ - applyEdits(edits: readonly EditType[]): Thenable; + applyEdits(document: CustomDocument, edits: readonly EditType[]): Thenable; /** * Undo a set of edits. * * This is triggered when a user undoes an edit. * + * @param document Document to undo edits from. * @param edit Array of edits. Sorted from most recent to oldest. * * @return Thenable signaling that the change has completed. */ - undoEdits(edits: readonly EditType[]): Thenable; + undoEdits(document: CustomDocument, edits: readonly EditType[]): Thenable; /** * Revert the file to its last saved state. * - * @param change Added or applied edits. + * @param document Document to revert. + * @param edits Added or applied edits. * * @return Thenable signaling that the change has completed. */ - revert(change: { - readonly undoneEdits: readonly EditType[]; - readonly appliedEdits: readonly EditType[]; - }): Thenable; + revert(document: CustomDocument, edits: CustomDocumentRevert): Thenable; /** * Back up the resource in its current state. @@ -1327,12 +1317,50 @@ declare module 'vscode' { * made in quick succession, `backup` is only triggered after the last one. `backup` is not invoked when * `auto save` is enabled (since auto save already persists resource ). * + * @param document Document to revert. * @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your * extension to decided how to respond to cancellation. If for example your extension is backing up a large file * in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather * than cancelling it to ensure that VS Code has some valid backup. */ - backup(cancellation: CancellationToken): Thenable; + backup(document: CustomDocument, cancellation: CancellationToken): Thenable; + } + + /** + * Event triggered by extensions to signal to VS Code that an edit has occurred on a CustomDocument``. + */ + interface CustomDocumentEditEvent { + /** + * Document the edit is for. + */ + readonly document: CustomDocument; + + /** + * Object that describes the edit. + * + * Edit objects are passed back to your extension in `undoEdits`, `applyEdits`, and `revert`. + */ + readonly edit: EditType; + + /** + * Display name describing the edit. + */ + readonly label?: string; + } + + /** + * Data about a revert for a `CustomDocument`. + */ + interface CustomDocumentRevert { + /** + * List of edits that were undone to get the document back to its on disk state. + */ + readonly undoneEdits: readonly EditType[]; + + /** + * List of edits that were reapplied to get the document back to its on disk state. + */ + readonly appliedEdits: readonly EditType[]; } /** @@ -1391,7 +1419,7 @@ declare module 'vscode' { * * @return The capabilities of the resolved document. */ - resolveCustomDocument(document: CustomDocument): Thenable; + resolveCustomDocument(document: CustomDocument): Thenable; /** * Resolve a webview editor for a given resource. @@ -1409,6 +1437,13 @@ declare module 'vscode' { * @return Thenable indicating that the webview editor has been resolved. */ resolveCustomEditor(document: CustomDocument, webviewPanel: WebviewPanel): Thenable; + + /** + * Defines the editing capability of a custom webview document. + * + * When not provided, the document is considered readonly. + */ + readonly editingDelegate?: CustomEditorEditingDelegate; } /** @@ -1454,7 +1489,7 @@ declare module 'vscode' { export function registerCustomEditorProvider( viewType: string, provider: CustomEditorProvider | CustomTextEditorProvider, - webviewOptions?: WebviewPanelOptions, + webviewOptions?: WebviewPanelOptions, // TODO: move this onto provider? ): Disposable; } diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 52ea58cb549..332017e9ee0 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -366,14 +366,14 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma return this._customEditorService.models.add(resource, viewType, model); } - public async $onDidEdit(resourceComponents: UriComponents, viewType: string, editId: number): Promise { + public async $onDidEdit(resourceComponents: UriComponents, viewType: string, editId: number, label: string | undefined): Promise { const resource = URI.revive(resourceComponents); const model = await this._customEditorService.models.get(resource, viewType); if (!model || !(model instanceof MainThreadCustomEditorModel)) { throw new Error('Could not find model for webview editor'); } - model.pushEdit(editId); + model.pushEdit(editId, label); } private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) { @@ -536,6 +536,8 @@ namespace HotExitState { export type State = typeof Allowed | typeof NotAllowed | Pending; } +const customDocumentFileScheme = 'custom'; + class MainThreadCustomEditorModel extends Disposable implements ICustomEditorModel, IWorkingCopy { private _hotExitState: HotExitState.State = HotExitState.Allowed; @@ -556,7 +558,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod constructor( private readonly _proxy: extHostProtocol.ExtHostWebviewsShape, private readonly _viewType: string, - private readonly _resource: URI, + private readonly _realResource: URI, private readonly _editable: boolean, @IWorkingCopyService workingCopyService: IWorkingCopyService, @ILabelService private readonly _labelService: ILabelService, @@ -564,6 +566,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod @IUndoRedoService private readonly _undoService: IUndoRedoService, ) { super(); + if (_editable) { this._register(workingCopyService.registerWorkingCopy(this)); } @@ -571,18 +574,26 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod dispose() { if (this._editable) { - this._undoService.removeElements(this.resource); + this._undoService.removeElements(this._realResource); } - this._proxy.$disposeWebviewCustomEditorDocument(this.resource, this._viewType); + this._proxy.$disposeWebviewCustomEditorDocument(this._realResource, this._viewType); super.dispose(); } //#region IWorkingCopy - public get resource() { return this._resource; } // custom://viewType/path/file + public get resource() { + // Make sure each custom editor has a unique resource for backup and edits + return URI.from({ + scheme: customDocumentFileScheme, + authority: this._viewType, + path: this._realResource.path, + query: JSON.stringify(this._realResource.toJSON()) + }); + } public get name() { - return basename(this._labelService.getUriLabel(this._resource)); + return basename(this._labelService.getUriLabel(this._realResource)); } public get capabilities(): WorkingCopyCapabilities { @@ -600,11 +611,12 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod readonly onDidChangeContent: Event = this._onDidChangeContent.event; //#endregion + public get viewType() { return this._viewType; } - public pushEdit(editId: number) { + public pushEdit(editId: number, label: string | undefined) { if (!this._editable) { throw new Error('Document is not editable'); } @@ -616,41 +628,45 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod this._undoService.pushElement({ type: UndoRedoElementType.Resource, - resource: this.resource, - label: 'Edit', // TODO: get this from extensions? - undo: async () => { - if (!this._editable) { - return; - } + resource: this._realResource, + label: label ?? localize('defaultEditLabel', "Edit"), + undo: () => this.undo(), + redo: () => this.redo(), + }); + } - if (this._currentEditIndex < 0) { - // nothing to undo - return; - } + private async undo(): Promise { + if (!this._editable) { + return; + } - const undoneEdit = this._edits[this._currentEditIndex]; - await this._proxy.$undo(this.resource, this.viewType, undoneEdit); + if (this._currentEditIndex < 0) { + // nothing to undo + return; + } - this.change(() => { - --this._currentEditIndex; - }); - }, - redo: async () => { - if (!this._editable) { - return; - } + const undoneEdit = this._edits[this._currentEditIndex]; + await this._proxy.$undo(this._realResource, this.viewType, undoneEdit); - if (this._currentEditIndex >= this._edits.length - 1) { - // nothing to redo - return; - } + this.change(() => { + --this._currentEditIndex; + }); + } - const redoneEdit = this._edits[this._currentEditIndex + 1]; - await this._proxy.$redo(this.resource, this.viewType, redoneEdit); - this.change(() => { - ++this._currentEditIndex; - }); - } + private async redo(): Promise { + if (!this._editable) { + return; + } + + if (this._currentEditIndex >= this._edits.length - 1) { + // nothing to redo + return; + } + + const redoneEdit = this._edits[this._currentEditIndex + 1]; + await this._proxy.$redo(this._realResource, this.viewType, redoneEdit); + this.change(() => { + ++this._currentEditIndex; }); } @@ -663,7 +679,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod : this._edits.splice(start, toRemove); if (removedEdits.length) { - this._proxy.$disposeEdits(this.resource, this._viewType, removedEdits); + this._proxy.$disposeEdits(this._realResource, this._viewType, removedEdits); } } @@ -695,7 +711,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod editsToRedo = this._edits.slice(this._currentEditIndex, this._savePoint); } - this._proxy.$revert(this.resource, this.viewType, { undoneEdits: editsToUndo, redoneEdits: editsToRedo }); + this._proxy.$revert(this._realResource, this.viewType, { undoneEdits: editsToUndo, redoneEdits: editsToRedo }); this.change(() => { this._currentEditIndex = this._savePoint; this.spliceEdits(); @@ -706,7 +722,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod if (!this._editable) { return false; } - await createCancelablePromise(token => this._proxy.$onSave(this.resource, this.viewType, token)); + await createCancelablePromise(token => this._proxy.$onSave(this._realResource, this.viewType, token)); this.change(() => { this._savePoint = this._currentEditIndex; }); @@ -715,7 +731,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod public async saveAs(resource: URI, targetResource: URI, _options?: ISaveOptions): Promise { if (this._editable) { - await this._proxy.$onSaveAs(this.resource, this.viewType, targetResource); + await this._proxy.$onSaveAs(this._realResource, this.viewType, targetResource); this.change(() => { this._savePoint = this._currentEditIndex; }); @@ -744,7 +760,7 @@ class MainThreadCustomEditorModel extends Disposable implements ICustomEditorMod const pendingState = new HotExitState.Pending( createCancelablePromise(token => - this._proxy.$backup(this.resource.toJSON(), this.viewType, token))); + this._proxy.$backup(this._realResource.toJSON(), this.viewType, token))); this._hotExitState = pendingState; try { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 45812459a6f..8efa4f7e543 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -67,6 +67,7 @@ export interface IEnvironment { userHome: URI; webviewResourceRoot: string; webviewCspSource: string; + useHostProxy?: boolean; } export interface IStaticWorkspaceData { @@ -594,7 +595,7 @@ export interface MainThreadWebviewsShape extends IDisposable { $registerCustomEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions): void; $unregisterEditorProvider(viewType: string): void; - $onDidEdit(resource: UriComponents, viewType: string, editId: number): void; + $onDidEdit(resource: UriComponents, viewType: string, editId: number, label: string | undefined): void; } export interface WebviewPanelViewStateData { diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 3324e32c8ae..f4dc637a2fe 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -5,7 +5,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import * as modes from 'vs/editor/common/modes'; @@ -248,25 +248,31 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa class CustomDocument extends Disposable implements vscode.CustomDocument { - public static create(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) { - return Object.seal(new CustomDocument(proxy, viewType, uri)); + public static create( + viewType: string, + uri: vscode.Uri, + editingDelegate: vscode.CustomEditorEditingDelegate | undefined + ) { + return Object.seal(new CustomDocument(viewType, uri, editingDelegate)); } // Explicitly initialize all properties as we seal the object after creation! readonly #_edits = new Cache('edits'); - readonly #proxy: MainThreadWebviewsShape; readonly #viewType: string; readonly #uri: vscode.Uri; + readonly #editingDelegate: vscode.CustomEditorEditingDelegate | undefined; - #capabilities: vscode.CustomEditorCapabilities | undefined = undefined; - - private constructor(proxy: MainThreadWebviewsShape, viewType: string, uri: vscode.Uri) { + private constructor( + viewType: string, + uri: vscode.Uri, + editingDelegate: vscode.CustomEditorEditingDelegate | undefined, + ) { super(); - this.#proxy = proxy; this.#viewType = viewType; this.#uri = uri; + this.#editingDelegate = editingDelegate; } dispose() { @@ -289,47 +295,35 @@ class CustomDocument extends Disposable implements vscode.CustomDocument { //#region Internal - /** @internal*/ _setCapabilities(capabilities: vscode.CustomEditorCapabilities) { - if (this.#capabilities) { - throw new Error('Capabilities already provided'); - } - - this.#capabilities = capabilities; - capabilities.editing?.onDidEdit(edit => { - const id = this.#_edits.add([edit]); - this.#proxy.$onDidEdit(this.uri, this.viewType, id); - }); - } - /** @internal*/ async _revert(changes: { undoneEdits: number[], redoneEdits: number[] }) { - const editing = this.getEditingCapability(); + const editing = this.getEditingDelegate(); const undoneEdits = changes.undoneEdits.map(id => this.#_edits.get(id, 0)); const appliedEdits = changes.redoneEdits.map(id => this.#_edits.get(id, 0)); - return editing.revert({ undoneEdits, appliedEdits }); + return editing.revert(this, { undoneEdits, appliedEdits }); } /** @internal*/ _undo(editId: number) { - const editing = this.getEditingCapability(); + const editing = this.getEditingDelegate(); const edit = this.#_edits.get(editId, 0); - return editing.undoEdits([edit]); + return editing.undoEdits(this, [edit]); } /** @internal*/ _redo(editId: number) { - const editing = this.getEditingCapability(); + const editing = this.getEditingDelegate(); const edit = this.#_edits.get(editId, 0); - return editing.applyEdits([edit]); + return editing.applyEdits(this, [edit]); } /** @internal*/ _save(cancellation: CancellationToken) { - return this.getEditingCapability().save(cancellation); + return this.getEditingDelegate().save(this, cancellation); } /** @internal*/ _saveAs(target: vscode.Uri) { - return this.getEditingCapability().saveAs(target); + return this.getEditingDelegate().saveAs(this, target); } /** @internal*/ _backup(cancellation: CancellationToken) { - return this.getEditingCapability().backup(cancellation); + return this.getEditingDelegate().backup(this, cancellation); } /** @internal*/ _disposeEdits(editIds: number[]) { @@ -338,13 +332,17 @@ class CustomDocument extends Disposable implements vscode.CustomDocument { } } + /** @internal*/ _pushEdit(edit: unknown): number { + return this.#_edits.add([edit]); + } + //#endregion - private getEditingCapability(): vscode.CustomEditorEditingCapability { - if (!this.#capabilities?.editing) { + private getEditingDelegate(): vscode.CustomEditorEditingDelegate { + if (!this.#editingDelegate) { throw new Error('Document is not editable'); } - return this.#capabilities.editing; + return this.#editingDelegate; } } @@ -487,17 +485,24 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { provider: vscode.CustomEditorProvider | vscode.CustomTextEditorProvider, options: vscode.WebviewPanelOptions | undefined = {} ): vscode.Disposable { - let disposable: vscode.Disposable; + const disposables = new DisposableStore(); if ('resolveCustomTextEditor' in provider) { - disposable = this._editorProviders.addTextProvider(viewType, extension, provider); + disposables.add(this._editorProviders.addTextProvider(viewType, extension, provider)); this._proxy.$registerTextEditorProvider(toExtensionData(extension), viewType, options); } else { - disposable = this._editorProviders.addCustomProvider(viewType, extension, provider); + disposables.add(this._editorProviders.addCustomProvider(viewType, extension, provider)); this._proxy.$registerCustomEditorProvider(toExtensionData(extension), viewType, options); + if (provider.editingDelegate) { + disposables.add(provider.editingDelegate.onDidEdit(e => { + const document = e.document; + const editId = (document as CustomDocument)._pushEdit(e.edit); + this._proxy.$onDidEdit(document.uri, document.viewType, editId, e.label); + })); + } } return VSCodeDisposable.from( - disposable, + disposables, new VSCodeDisposable(() => { this._proxy.$unregisterEditorProvider(viewType); })); @@ -592,12 +597,11 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } const revivedResource = URI.revive(resource); - const document = CustomDocument.create(this._proxy, viewType, revivedResource); - const capabilities = await entry.provider.resolveCustomDocument(document); - document._setCapabilities(capabilities); + const document = CustomDocument.create(viewType, revivedResource, entry.provider.editingDelegate); + await entry.provider.resolveCustomDocument(document); this._documents.add(document); return { - editable: !!capabilities.editing + editable: !!entry.provider.editingDelegate, }; } diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 79189ba670b..3a02c5ce0b7 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -61,7 +61,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService { // Do this when extension service exists, but extensions are not being activated yet. const configProvider = await this._extHostConfiguration.getConfigProvider(); - await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy); + await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData); // Use IPC messages to forward console-calls, note that the console is // already patched to use`process.send()` diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index a394c7d34a4..8956c189255 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -24,11 +24,23 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { withNullAsUndefined } from 'vs/base/common/types'; export interface IResourceLabelProps { - resource?: URI; + resource?: URI | { master?: URI, detail?: URI }; name?: string | string[]; description?: string; } +function toResource(props: IResourceLabelProps | undefined): URI | undefined { + if (!props || !props.resource) { + return undefined; + } + + if (URI.isUri(props.resource)) { + return props.resource; + } + + return props.resource.master; +} + export interface IResourceLabelOptions extends IIconLabelValueOptions { fileKind?: FileKind; fileDecorations?: { colors: boolean, badges: boolean }; @@ -289,11 +301,16 @@ class ResourceLabelWidget extends IconLabel { } notifyFileDecorationsChanges(e: IResourceDecorationChangeEvent): void { - if (!this.options || !this.label || !this.label.resource) { + if (!this.options) { return; } - if (this.options.fileDecorations && e.affectsResource(this.label.resource)) { + const resource = toResource(this.label); + if (!resource) { + return; + } + + if (this.options.fileDecorations && e.affectsResource(resource)) { this.render(false); } } @@ -311,13 +328,13 @@ class ResourceLabelWidget extends IconLabel { } notifyFormattersChange(scheme: string): void { - if (this.label?.resource?.scheme === scheme) { + if (toResource(this.label)?.scheme === scheme) { this.render(false); } } notifyUntitledLabelChange(resource: URI): void { - if (isEqual(resource, this.label?.resource)) { + if (isEqual(resource, toResource(this.label))) { this.render(false); } } @@ -347,7 +364,10 @@ class ResourceLabelWidget extends IconLabel { } setResource(label: IResourceLabelProps, options: IResourceLabelOptions = Object.create(null)): void { - if (label.resource?.scheme === Schemas.untitled) { + const resource = toResource(this.label); + const isMasterDetail = this.label?.resource && !URI.isUri(this.label.resource); + + if (!isMasterDetail && resource?.scheme === Schemas.untitled) { // Untitled labels are very dynamic because they may change // whenever the content changes (unless a path is associated). // As such we always ask the actual editor for it's name and @@ -355,7 +375,11 @@ class ResourceLabelWidget extends IconLabel { // provided. If they are not provided from the label we got // we assume that the client does not want to display them // and as such do not override. - const untitledModel = this.textFileService.untitled.get(label.resource); + // + // We do not touch the label if it represents a master-detail + // because in that case we expect it to carry a proper label + // and description. + const untitledModel = this.textFileService.untitled.get(resource); if (untitledModel && !untitledModel.hasAssociatedFilePath) { if (typeof label.name === 'string') { label.name = untitledModel.name; @@ -415,7 +439,7 @@ class ResourceLabelWidget extends IconLabel { } private hasPathLabelChanged(newLabel: IResourceLabelProps, newOptions?: IResourceLabelOptions): boolean { - const newResource = newLabel ? newLabel.resource : undefined; + const newResource = toResource(newLabel); return !!newResource && this.computedPathLabel !== this.labelService.getUriLabel(newResource); } @@ -444,7 +468,8 @@ class ResourceLabelWidget extends IconLabel { } if (this.label) { - const detectedModeId = this.label.resource ? withNullAsUndefined(detectModeId(this.modelService, this.modeService, this.label.resource)) : undefined; + const resource = toResource(this.label); + const detectedModeId = resource ? withNullAsUndefined(detectModeId(this.modelService, this.modeService, resource)) : undefined; if (this.lastKnownDetectedModeId !== detectedModeId) { clearIconCache = true; this.lastKnownDetectedModeId = detectedModeId; @@ -470,7 +495,7 @@ class ResourceLabelWidget extends IconLabel { domId: this.options?.domId }; - const resource = this.label.resource; + const resource = toResource(this.label); const label = this.label.name; if (this.options && typeof this.options.title === 'string') { diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 211fdec8ce1..3af508ded81 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -260,9 +260,6 @@ export class NoTabsTitleControl extends TitleControl { this.updateEditorDirty(editor); // Editor Label - const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - const name = editor.getName(); - const { labelFormat } = this.accessor.partOptions; let description: string; if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) { @@ -278,7 +275,19 @@ export class NoTabsTitleControl extends TitleControl { title = ''; // dont repeat what is already shown } - editorLabel.setResource({ name, description, resource }, { title: typeof title === 'string' ? title : undefined, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] }); + editorLabel.setResource( + { + resource: toResource(editor, { supportSideBySide: SideBySideEditor.BOTH }), + name: editor.getName(), + description + }, + { + title, + italic: !isEditorPinned, + extraClasses: ['no-tabs', 'title-label'] + } + ); + if (isGroupActive) { editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND) || ''; } else { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index d5042465900..c85a70573e1 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -963,10 +963,13 @@ export class TabsTitleControl extends TitleControl { tabContainer.title = title; // Label - const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); - tabLabelWidget.setResource({ name, description, resource }, { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) }); + tabLabelWidget.setResource( + { name, description, resource: toResource(editor, { supportSideBySide: SideBySideEditor.BOTH }) }, + { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) } + ); // Tests helper + const resource = toResource(editor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { tabContainer.setAttribute('data-resource-name', basenameOrAuthority(resource)); } else { diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 19cd1f14f2b..d6384439f03 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -151,7 +151,10 @@ class NotificationMessageRenderer { const anchor = $('a', { href: node.href, title: title, }, node.label); if (actionHandler) { - actionHandler.toDispose.add(addDisposableListener(anchor, EventType.CLICK, () => actionHandler.callback(node.href))); + actionHandler.toDispose.add(addDisposableListener(anchor, EventType.CLICK, e => { + EventHelper.stop(e, true); + actionHandler.callback(node.href); + })); } messageContainer.appendChild(anchor); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index a6ecc28c537..854a32e9e3c 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1319,7 +1319,8 @@ export interface IEditorPartOptionsChangeEvent { export enum SideBySideEditor { MASTER = 1, - DETAILS = 2 + DETAILS = 2, + BOTH = 3 } export interface IResourceOptions { @@ -1327,12 +1328,22 @@ export interface IResourceOptions { filterByScheme?: string | string[]; } -export function toResource(editor: IEditorInput | undefined, options?: IResourceOptions): URI | undefined { +export function toResource(editor: IEditorInput | undefined): URI | undefined; +export function toResource(editor: IEditorInput | undefined, options: IResourceOptions & { supportSideBySide?: SideBySideEditor.MASTER | SideBySideEditor.DETAILS }): URI | undefined; +export function toResource(editor: IEditorInput | undefined, options: IResourceOptions & { supportSideBySide: SideBySideEditor.BOTH }): URI | { master?: URI, detail?: URI } | undefined; +export function toResource(editor: IEditorInput | undefined, options?: IResourceOptions): URI | { master?: URI, detail?: URI } | undefined { if (!editor) { return undefined; } if (options?.supportSideBySide && editor instanceof SideBySideEditorInput) { + if (options?.supportSideBySide === SideBySideEditor.BOTH) { + return { + master: toResource(editor.master, { filterByScheme: options.filterByScheme }), + detail: toResource(editor.details, { filterByScheme: options.filterByScheme }) + }; + } + editor = options.supportSideBySide === SideBySideEditor.MASTER ? editor.master : editor.details; } @@ -1341,12 +1352,14 @@ export function toResource(editor: IEditorInput | undefined, options?: IResource return resource; } - if (Array.isArray(options.filterByScheme) && options.filterByScheme.some(scheme => resource.scheme === scheme)) { - return resource; - } - - if (options.filterByScheme === resource.scheme) { - return resource; + if (Array.isArray(options.filterByScheme)) { + if (options.filterByScheme.some(scheme => resource.scheme === scheme)) { + return resource; + } + } else { + if (options.filterByScheme === resource.scheme) { + return resource; + } } return undefined; diff --git a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index ddcd8ff16b4..e26553e54b7 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -10,6 +10,7 @@ import './inspectKeybindings'; import './largeFileOptimizations'; import './inspectEditorTokens/inspectEditorTokens'; import './quickaccess/gotoLineQuickAccess'; +import './quickaccess/gotoSymbolAccess'; import './saveParticipants'; import './toggleColumnSelection'; import './toggleMinimap'; diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index e50b43138e3..36ce9d91f9f 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -26,7 +26,7 @@ import { findMatchingThemeRule } from 'vs/workbench/services/textMate/common/TMH import { ITextMateService, IGrammar, IToken, StackElement } from 'vs/workbench/services/textMate/common/textMateService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition } from 'vs/workbench/services/themes/common/colorThemeData'; +import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition, TextMateThemingRuleDefinitions } from 'vs/workbench/services/themes/common/colorThemeData'; import { TokenStylingRule, TokenStyleData, TokenStyle } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -303,7 +303,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const allDefValues = []; // remember the order // first collect to detect when the same rule is used fro multiple properties for (let property of properties) { - if (semanticTokenInfo.metadata[property]) { + if (semanticTokenInfo.metadata[property] !== undefined) { const definition = semanticTokenInfo.definitions[property]; const defValue = this._renderTokenStyleDefinition(definition, property); let properties = propertiesByDefValue[defValue]; @@ -532,11 +532,11 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const isTokenStylingRule = (d: any): d is TokenStylingRule => !!d.value; if (Array.isArray(definition)) { - for (const d of definition) { - const matchingRule = findMatchingThemeRule(theme, d, false); - if (matchingRule) { - return `${escape(d.join(' '))}
${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; - } + const scopesDefinition: TextMateThemingRuleDefinitions = {}; + theme.resolveScopes(definition, scopesDefinition); + const matchingRule = scopesDefinition[property]; + if (matchingRule && scopesDefinition.scope) { + return `${escape(scopesDefinition.scope.join(' '))}
${matchingRule.scope}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; } return ''; } else if (isTokenStylingRule(definition)) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts new file mode 100644 index 00000000000..eee2fd66239 --- /dev/null +++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolAccess.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { IKeyMods } from 'vs/platform/quickinput/common/quickInput'; +import { IEditor } from 'vs/editor/common/editorCommon'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { IRange } from 'vs/editor/common/core/range'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess'; +import { AbstractGotoSymbolQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess'; + +export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider { + + readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange; + + constructor(@IEditorService private readonly editorService: IEditorService) { + super(); + } + + get activeTextEditorControl() { + return this.editorService.activeTextEditorControl; + } + + protected gotoSymbol(editor: IEditor, range: IRange, keyMods: IKeyMods): void { + + // Check for sideBySide use + if (keyMods.ctrlCmd && this.editorService.activeEditor) { + this.editorService.openEditor(this.editorService.activeEditor, { selection: range, pinned: keyMods.alt }, SIDE_GROUP); + } + + // Otherwise let parent handle it + else { + super.gotoSymbol(editor, range, keyMods); + } + } +} + +Registry.as(Extensions.Quickaccess).registerQuickAccessProvider({ + ctor: GotoSymbolQuickAccessProvider, + prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, + placeholder: localize('gotoSymbolQuickAccessPlaceholder', "Type the name of a symbol to go to."), + helpEntries: [ + { description: localize('gotoSymbolQuickAccess', "Go to Symol in Editor"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, needsEditor: true }, + { description: localize('gotoSymbolByCategoryQuickAccess', "Go to Symol in Editor by Category"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true } + ] +}); diff --git a/src/vs/workbench/contrib/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts index ca025ac0c76..bc94f6616fb 100644 --- a/src/vs/workbench/contrib/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -400,7 +400,8 @@ export class CopyValueAction extends Action { if (stackFrame && session && this.value.evaluateName) { try { - const evaluation = await session.evaluate(this.value.evaluateName, stackFrame.frameId, this.context); + const context = session.capabilities.supportsClipboardContext ? 'clipboard' : this.context; + const evaluation = await session.evaluate(this.value.evaluateName, stackFrame.frameId, context); this.clipboardService.writeText(evaluation.body.result); } catch (e) { this.clipboardService.writeText(this.value.value); diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 1c22f7201cd..9d089f8261b 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -592,7 +592,7 @@ class OpenEditorRenderer implements IListRenderer('backupFileService'); export interface IResolvedBackup { - value: ITextBufferFactory; - meta?: T; + readonly value: ITextBufferFactory; + readonly meta?: T; } /** diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 70282328684..32c51b41e3a 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -289,6 +289,11 @@ export const schema: IJSONSchema = { body: 'onUri', description: nls.localize('vscode.extension.activationEvents.onUri', 'An activation event emitted whenever a system-wide Uri directed towards this extension is open.'), }, + { + label: 'onCustomEditor', + body: 'onCustomEditor:${9:viewType}', + description: nls.localize('vscode.extension.activationEvents.onCustomEditor', 'An activation event emitted whenever the specified custom editor becomes visible.'), + }, { label: '*', description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on VS Code startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'), diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index 0f35c544319..e35e1736651 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -25,6 +25,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; interface ParsedExtHostArgs { uriTransformerPath?: string; + useHostProxy?: string; } // workaround for https://github.com/microsoft/vscode/issues/85490 @@ -40,7 +41,8 @@ interface ParsedExtHostArgs { const args = minimist(process.argv.slice(2), { string: [ - 'uriTransformerPath' + 'uriTransformerPath', + 'useHostProxy' ] }) as ParsedExtHostArgs; @@ -293,6 +295,7 @@ export async function startExtensionHostProcess(): Promise { const { initData } = renderer; // setup things patchProcess(!!initData.environment.extensionTestsLocationURI); // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708) + initData.environment.useHostProxy = args.useHostProxy !== undefined ? args.useHostProxy !== 'false' : undefined; // host abstraction const hostUtils = new class NodeHost implements IHostUtils { diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 4c72df591f5..ad9308548a7 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -16,7 +16,7 @@ import { endsWith } from 'vs/base/common/strings'; import { IExtHostWorkspaceProvider } from 'vs/workbench/api/common/extHostWorkspace'; import { ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration'; import { ProxyAgent } from 'vscode-proxy-agent'; -import { MainThreadTelemetryShape } from 'vs/workbench/api/common/extHost.protocol'; +import { MainThreadTelemetryShape, IInitData } from 'vs/workbench/api/common/extHost.protocol'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { URI } from 'vs/base/common/uri'; @@ -35,9 +35,10 @@ export function connectProxyResolver( configProvider: ExtHostConfigProvider, extensionService: ExtHostExtensionService, extHostLogService: ILogService, - mainThreadTelemetry: MainThreadTelemetryShape + mainThreadTelemetry: MainThreadTelemetryShape, + initData: IInitData, ) { - const resolveProxy = setupProxyResolution(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry); + const resolveProxy = setupProxyResolution(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry, initData); const lookup = createPatchedModules(configProvider, resolveProxy); return configureModuleLoading(extensionService, lookup); } @@ -48,7 +49,8 @@ function setupProxyResolution( extHostWorkspace: IExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider, extHostLogService: ILogService, - mainThreadTelemetry: MainThreadTelemetryShape + mainThreadTelemetry: MainThreadTelemetryShape, + initData: IInitData, ) { const env = process.env; @@ -139,12 +141,14 @@ function setupProxyResolution( timeout = setTimeout(logEvent, 10 * 60 * 1000); } + const useHostProxy = initData.environment.useHostProxy; + const doUseHostProxy = typeof useHostProxy === 'boolean' ? useHostProxy : !initData.remote.isRemote; useSystemCertificates(extHostLogService, flags.useSystemCertificates, opts, () => { - useProxySettings(flags.useProxySettings, req, opts, url, callback); + useProxySettings(doUseHostProxy, flags.useProxySettings, req, opts, url, callback); }); } - function useProxySettings(useProxySettings: boolean, req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) { + function useProxySettings(useHostProxy: boolean, useProxySettings: boolean, req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) { if (!useProxySettings) { callback('DIRECT'); @@ -192,6 +196,12 @@ function setupProxyResolution( return; } + if (!useHostProxy) { + callback('DIRECT'); + extHostLogService.trace('ProxyResolver#resolveProxy unconfigured', url, 'DIRECT'); + return; + } + const start = Date.now(); extHostWorkspace.resolveProxy(url) // Use full URL to ensure it is an actually used one. .then(proxy => { diff --git a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts index 41e6b4e34c4..0df88daa425 100644 --- a/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts @@ -28,6 +28,9 @@ export class BrowserLifecycleService extends AbstractLifecycleService { } private onBeforeUnload(): string | null { + const logService = this.logService; + logService.info('[lifecycle] onBeforeUnload triggered'); + let veto = false; // Before Shutdown @@ -36,7 +39,7 @@ export class BrowserLifecycleService extends AbstractLifecycleService { if (value === true) { veto = true; } else if (value instanceof Promise && !veto) { - console.warn(new Error('Long running onBeforeShutdown currently not supported in the web')); + logService.error('[lifecycle] Long running onBeforeShutdown currently not supported in the web'); veto = true; } }, @@ -51,7 +54,7 @@ export class BrowserLifecycleService extends AbstractLifecycleService { // No Veto: continue with Will Shutdown this._onWillShutdown.fire({ join() { - console.warn(new Error('Long running onWillShutdown currently not supported in the web')); + logService.error('[lifecycle] Long running onWillShutdown currently not supported in the web'); }, reason: ShutdownReason.QUIT }); diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index bacaa95f9f7..80e070d65dd 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -44,6 +44,8 @@ const tokenGroupToScopesMap = { export type TokenStyleDefinition = TokenStylingRule | ProbeScope[] | TokenStyleValue; export type TokenStyleDefinitions = { [P in keyof TokenStyleData]?: TokenStyleDefinition | undefined }; +export type TextMateThemingRuleDefinitions = { [P in keyof TokenStyleData]?: ITextMateThemingRule | undefined; } & { scope?: ProbeScope; }; + const PERSISTED_THEME_STORAGE_KEY = 'colorThemeData'; export class ColorThemeData implements IWorkbenchColorTheme { @@ -271,7 +273,8 @@ export class ColorThemeData implements IWorkbenchColorTheme { return colorRegistry.resolveDefaultColor(colorId, this); } - public resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined { + + public resolveScopes(scopes: ProbeScope[], definitions?: TextMateThemingRuleDefinitions): TokenStyle | undefined { if (!this.themeTokenScopeMatchers) { this.themeTokenScopeMatchers = this.themeTokenColors.map(getScopeMatcher); @@ -285,19 +288,24 @@ export class ColorThemeData implements IWorkbenchColorTheme { let fontStyle: string | undefined = undefined; let foregroundScore = -1; let fontStyleScore = -1; + let fontStyleThemingRule: ITextMateThemingRule | undefined = undefined; + let foregroundThemingRule: ITextMateThemingRule | undefined = undefined; - function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITextMateThemingRule[]) { + function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], themingRules: ITextMateThemingRule[]) { for (let i = 0; i < scopeMatchers.length; i++) { const score = scopeMatchers[i](scope); if (score >= 0) { - const settings = tokenColors[i].settings; + const themingRule = themingRules[i]; + const settings = themingRules[i].settings; if (score >= foregroundScore && settings.foreground) { foreground = settings.foreground; foregroundScore = score; + foregroundThemingRule = themingRule; } if (score >= fontStyleScore && types.isString(settings.fontStyle)) { fontStyle = settings.fontStyle; fontStyleScore = score; + fontStyleThemingRule = themingRule; } } } @@ -305,6 +313,12 @@ export class ColorThemeData implements IWorkbenchColorTheme { findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors); findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors); if (foreground !== undefined || fontStyle !== undefined) { + if (definitions) { + definitions.foreground = foregroundThemingRule; + definitions.bold = definitions.italic = definitions.underline = fontStyleThemingRule; + definitions.scope = scope; + } + return TokenStyle.fromSettings(foreground, fontStyle); } } diff --git a/src/vs/workbench/test/browser/parts/editor/editor.test.ts b/src/vs/workbench/test/browser/parts/editor/editor.test.ts index 96f97d1ad98..e974da14e70 100644 --- a/src/vs/workbench/test/browser/parts/editor/editor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editor.test.ts @@ -35,6 +35,8 @@ suite('Workbench editor', () => { assert.equal(toResource(untitled)!.toString(), untitled.resource.toString()); assert.equal(toResource(untitled, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), untitled.resource.toString()); + assert.equal(toResource(untitled, { supportSideBySide: SideBySideEditor.DETAILS })!.toString(), untitled.resource.toString()); + assert.equal(toResource(untitled, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), untitled.resource.toString()); assert.equal(toResource(untitled, { filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString()); assert.equal(toResource(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString()); assert.ok(!toResource(untitled, { filterByScheme: Schemas.file })); @@ -43,6 +45,8 @@ suite('Workbench editor', () => { assert.equal(toResource(file)!.toString(), file.resource.toString()); assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.resource.toString()); + assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.DETAILS })!.toString(), file.resource.toString()); + assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), file.resource.toString()); assert.equal(toResource(file, { filterByScheme: Schemas.file })!.toString(), file.resource.toString()); assert.equal(toResource(file, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString()); assert.ok(!toResource(file, { filterByScheme: Schemas.untitled })); @@ -52,8 +56,20 @@ suite('Workbench editor', () => { assert.ok(!toResource(diffEditorInput)); assert.ok(!toResource(diffEditorInput, { filterByScheme: Schemas.file })); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.resource.toString()); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file })!.toString(), file.resource.toString()); - assert.equal(toResource(file, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.MASTER })!.toString(), file.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file })!.toString(), file.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString()); + + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.DETAILS })!.toString(), untitled.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.DETAILS, filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString()); + assert.equal(toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.DETAILS, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString()); + + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH }) as { master: URI, detail: URI }).master.toString(), file.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.file }) as { master: URI, detail: URI }).master.toString(), file.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: [Schemas.file, Schemas.untitled] }) as { master: URI, detail: URI }).master.toString(), file.resource.toString()); + + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH }) as { master: URI, detail: URI }).detail.toString(), untitled.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.untitled }) as { master: URI, detail: URI }).detail.toString(), untitled.resource.toString()); + assert.equal((toResource(diffEditorInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: [Schemas.file, Schemas.untitled] }) as { master: URI, detail: URI }).detail.toString(), untitled.resource.toString()); }); });