From 8db09a42ff4670fdb600d503c687d0ea488b03b1 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 13 Sep 2019 12:15:50 +0200 Subject: [PATCH 01/49] introducing tokenScopes --- .../browser/standaloneThemeServiceImpl.ts | 6 + .../test/browser/standaloneLanguages.test.ts | 5 +- src/vs/platform/theme/common/themeService.ts | 3 + .../theme/common/tokenStyleRegistry.ts | 268 ++++++++++++++++++ .../theme/test/common/testThemeService.ts | 5 + .../terminalColorRegistry.test.ts | 6 +- .../services/themes/common/colorThemeData.ts | 153 +++++++++- .../themes/common/textMateScopeMatcher.ts | 111 ++++++++ .../tokenStyleResolving.test.ts | 89 ++++++ 9 files changed, 640 insertions(+), 6 deletions(-) create mode 100644 src/vs/platform/theme/common/tokenStyleRegistry.ts create mode 100644 src/vs/workbench/services/themes/common/textMateScopeMatcher.ts create mode 100644 src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 613ec87b2de..b26e44bf76c 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -14,6 +14,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { Registry } from 'vs/platform/registry/common/platform'; import { ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ThemingExtensions, ICssStyleCollector, IIconTheme, IThemingRegistry } from 'vs/platform/theme/common/themeService'; +import { TokenStyle, TokenStyleIdentifier } from 'vs/platform/theme/common/tokenStyleRegistry'; const VS_THEME_NAME = 'vs'; const VS_DARK_THEME_NAME = 'vs-dark'; @@ -23,6 +24,7 @@ const colorRegistry = Registry.as(Extensions.ColorContribution); const themingRegistry = Registry.as(ThemingExtensions.ThemingContribution); class StandaloneTheme implements IStandaloneTheme { + public readonly id: string; public readonly themeName: string; @@ -128,6 +130,10 @@ class StandaloneTheme implements IStandaloneTheme { } return this._tokenTheme; } + + getTokenStyle(tokenStyle: TokenStyleIdentifier, useDefault?: boolean | undefined): TokenStyle | undefined { + return undefined; + } } function isBuiltinTheme(themeName: string): themeName is BuiltinTheme { diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index b35c23cc03a..5dfa7d92149 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -13,6 +13,7 @@ import { ILineTokens, IToken, TokenizationSupport2Adapter, TokensProvider } from import { IStandaloneTheme, IStandaloneThemeData, IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { IIconTheme, ITheme, LIGHT } from 'vs/platform/theme/common/themeService'; +import { TokenStyleIdentifier } from 'vs/platform/theme/common/tokenStyleRegistry'; suite('TokenizationSupport2Adapter', () => { @@ -54,7 +55,9 @@ suite('TokenizationSupport2Adapter', () => { defines: (color: ColorIdentifier): boolean => { throw new Error('Not implemented'); - } + }, + + getTokenStyle: (tokenStyleId: TokenStyleIdentifier) => undefined }; } public getIconTheme(): IIconTheme { diff --git a/src/vs/platform/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index 42e93300a85..7ac9788d1a7 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -10,6 +10,7 @@ import * as platform from 'vs/platform/registry/common/platform'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Event, Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { TokenStyleIdentifier, TokenStyle } from 'vs/platform/theme/common/tokenStyleRegistry'; export const IThemeService = createDecorator('themeService'); @@ -59,6 +60,8 @@ export interface ITheme { * default color will be used. */ defines(color: ColorIdentifier): boolean; + + getTokenStyle(color: TokenStyleIdentifier, useDefault?: boolean): TokenStyle | undefined; } export interface IIconTheme { diff --git a/src/vs/platform/theme/common/tokenStyleRegistry.ts b/src/vs/platform/theme/common/tokenStyleRegistry.ts new file mode 100644 index 00000000000..5f593d41370 --- /dev/null +++ b/src/vs/platform/theme/common/tokenStyleRegistry.ts @@ -0,0 +1,268 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as platform from 'vs/platform/registry/common/platform'; +import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; +import { Color } from 'vs/base/common/color'; +import { ITheme } from 'vs/platform/theme/common/themeService'; +import { Event, Emitter } from 'vs/base/common/event'; + +import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { RunOnceScheduler } from 'vs/base/common/async'; + +// ------ API types + +export type TokenStyleIdentifier = string; + +export interface TokenStyleContribution { + readonly id: TokenStyleIdentifier; + readonly description: string; + readonly defaults: TokenStyleDefaults | null; + readonly deprecationMessage: string | undefined; +} + +export const enum TokenStyleBits { + BOLD = 0x01, + UNDERLINE = 0x02, + ITALIC = 0x04 +} + +export class TokenStyle { + constructor( + public readonly foreground?: Color, + public readonly background?: Color, + public readonly styles?: number + ) { + + } + + hasStyle(style: number): boolean { + return !!this.styles && ((this.styles & style) === style); + } +} + +export namespace TokenStyle { + export function fromString(s: string) { + const parts = s.split('-'); + let part = parts.shift(); + if (part) { + const foreground = Color.fromHex(part); + let background = undefined; + let style = undefined; + part = parts.shift(); + if (part && part[0] === '#') { + background = Color.fromHex(part); + part = parts.shift(); + } + if (part) { + try { + style = parseInt(part); + } catch (e) { + // ignore + } + } + return new TokenStyle(foreground, background, style); + } + return new TokenStyle(Color.red); + } +} + + + +export interface TokenStyleFunction { + (theme: ITheme): TokenStyle | undefined; +} + +export interface TokenStyleDefaults { + scopesToProbe?: string[]; + light: TokenStyle | null; + dark: TokenStyle | null; + hc: TokenStyle | null; +} + +/** + * A TokenStyle Value is either a tokestyle literal, a reference to other color or a derived color + */ +export type TokenStyleValue = TokenStyle | string | TokenStyleIdentifier | TokenStyleFunction; + +// TokenStyle registry +export const Extensions = { + TokenStyleContribution: 'base.contributions.tokenStyles' +}; + +export interface ITokenStyleRegistry { + + readonly onDidChangeSchema: Event; + + /** + * Register a TokenStyle to the registry. + * @param id The TokenStyle id as used in theme description files + * @param defaults The default values + * @description the description + */ + registerTokenStyle(id: string, defaults: TokenStyleDefaults, description: string): TokenStyleIdentifier; + + /** + * Register a TokenStyle to the registry. + */ + deregisterTokenStyle(id: string): void; + + /** + * Get all TokenStyle contributions + */ + getTokenStyles(): TokenStyleContribution[]; + + /** + * Gets the default TokenStyle of the given id + */ + resolveDefaultTokenStyle(id: TokenStyleIdentifier, theme: ITheme, findTokenStyleForScope: (scope: string) => TokenStyle | undefined): TokenStyle | undefined; + + /** + * JSON schema for an object to assign TokenStyle values to one of the TokenStyle contributions. + */ + getTokenStyleSchema(): IJSONSchema; + + /** + * JSON schema to for a reference to a TokenStyle contribution. + */ + getTokenStyleReferenceSchema(): IJSONSchema; + +} + + + +class TokenStyleRegistry implements ITokenStyleRegistry { + + private readonly _onDidChangeSchema = new Emitter(); + readonly onDidChangeSchema: Event = this._onDidChangeSchema.event; + + private tokenStyleById: { [key: string]: TokenStyleContribution }; + private tokenStyleSchema: IJSONSchema & { properties: IJSONSchemaMap } = { type: 'object', properties: {} }; + private tokenStyleReferenceSchema: IJSONSchema & { enum: string[], enumDescriptions: string[] } = { type: 'string', enum: [], enumDescriptions: [] }; + + constructor() { + this.tokenStyleById = {}; + } + + public registerTokenStyle(id: string, defaults: TokenStyleDefaults | null, description: string, deprecationMessage?: string): TokenStyleIdentifier { + let colorContribution: TokenStyleContribution = { id, description, defaults, deprecationMessage }; + this.tokenStyleById[id] = colorContribution; + let propertySchema: IJSONSchema = { type: 'string', description, format: 'color-hex', default: '#ff0000' }; + if (deprecationMessage) { + propertySchema.deprecationMessage = deprecationMessage; + } + this.tokenStyleSchema.properties[id] = propertySchema; + this.tokenStyleReferenceSchema.enum.push(id); + this.tokenStyleReferenceSchema.enumDescriptions.push(description); + + this._onDidChangeSchema.fire(); + return id; + } + + + public deregisterTokenStyle(id: string): void { + delete this.tokenStyleById[id]; + delete this.tokenStyleSchema.properties[id]; + const index = this.tokenStyleReferenceSchema.enum.indexOf(id); + if (index !== -1) { + this.tokenStyleReferenceSchema.enum.splice(index, 1); + this.tokenStyleReferenceSchema.enumDescriptions.splice(index, 1); + } + this._onDidChangeSchema.fire(); + } + + public getTokenStyles(): TokenStyleContribution[] { + return Object.keys(this.tokenStyleById).map(id => this.tokenStyleById[id]); + } + + public resolveDefaultTokenStyle(id: TokenStyleIdentifier, theme: ITheme, findTokenStyleForScope: (scope: string) => TokenStyle | undefined): TokenStyle | undefined { + const tokenStyleDesc = this.tokenStyleById[id]; + if (tokenStyleDesc && tokenStyleDesc.defaults) { + const scopesToProbe = tokenStyleDesc.defaults.scopesToProbe; + if (scopesToProbe) { + for (let scope of scopesToProbe) { + const style = findTokenStyleForScope(scope); + if (style) { + return style; + } + } + } + const tokenStyleValue = tokenStyleDesc.defaults[theme.type]; + return resolveTokenStyleValue(tokenStyleValue, theme); + } + return undefined; + } + + public getTokenStyleSchema(): IJSONSchema { + return this.tokenStyleSchema; + } + + public getTokenStyleReferenceSchema(): IJSONSchema { + return this.tokenStyleReferenceSchema; + } + + public toString() { + let sorter = (a: string, b: string) => { + let cat1 = a.indexOf('.') === -1 ? 0 : 1; + let cat2 = b.indexOf('.') === -1 ? 0 : 1; + if (cat1 !== cat2) { + return cat1 - cat2; + } + return a.localeCompare(b); + }; + + return Object.keys(this.tokenStyleById).sort(sorter).map(k => `- \`${k}\`: ${this.tokenStyleById[k].description}`).join('\n'); + } + +} + +const tokenStyleRegistry = new TokenStyleRegistry(); +platform.Registry.add(Extensions.TokenStyleContribution, tokenStyleRegistry); + +export function registerTokenStyle(id: string, defaults: TokenStyleDefaults | null, description: string, deprecationMessage?: string): TokenStyleIdentifier { + return tokenStyleRegistry.registerTokenStyle(id, defaults, description, deprecationMessage); +} + +export function getTokenStyleRegistry(): ITokenStyleRegistry { + return tokenStyleRegistry; +} + +// ----- implementation + +/** + * @param colorValue Resolve a color value in the context of a theme + */ +function resolveTokenStyleValue(tokenStyleValue: TokenStyleValue | null, theme: ITheme): TokenStyle | undefined { + if (tokenStyleValue === null) { + return undefined; + } else if (typeof tokenStyleValue === 'string') { + if (tokenStyleValue[0] === '#') { + return TokenStyle.fromString(tokenStyleValue); + } + return theme.getTokenStyle(tokenStyleValue); + } else if (typeof tokenStyleValue === 'object') { + return tokenStyleValue; + } else if (typeof tokenStyleValue === 'function') { + return tokenStyleValue(theme); + } + return undefined; +} + +export const tokenStyleColorsSchemaId = 'vscode://schemas/workbench-tokenstyles'; + +let schemaRegistry = platform.Registry.as(JSONExtensions.JSONContribution); +schemaRegistry.registerSchema(tokenStyleColorsSchemaId, tokenStyleRegistry.getTokenStyleSchema()); + +const delayer = new RunOnceScheduler(() => schemaRegistry.notifySchemaChanged(tokenStyleColorsSchemaId), 200); +tokenStyleRegistry.onDidChangeSchema(() => { + if (!delayer.isScheduled()) { + delayer.schedule(); + } +}); + +// setTimeout(_ => console.log(colorRegistry.toString()), 5000); + + + diff --git a/src/vs/platform/theme/test/common/testThemeService.ts b/src/vs/platform/theme/test/common/testThemeService.ts index fd49d393a34..e384b457389 100644 --- a/src/vs/platform/theme/test/common/testThemeService.ts +++ b/src/vs/platform/theme/test/common/testThemeService.ts @@ -6,6 +6,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IThemeService, ITheme, DARK, IIconTheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; +import { TokenStyle, TokenStyleIdentifier } from 'vs/platform/theme/common/tokenStyleRegistry'; export class TestTheme implements ITheme { @@ -23,6 +24,10 @@ export class TestTheme implements ITheme { defines(color: string): boolean { throw new Error('Method not implemented.'); } + + getTokenStyle(tokenStyleIdentifier: TokenStyleIdentifier, useDefault?: boolean | undefined): TokenStyle | undefined { + return undefined; + } } export class TestIconTheme implements IIconTheme { diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts index bf413e442d4..d2d64fa1a24 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts @@ -9,6 +9,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ansiColorIdentifiers, registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { ITheme, ThemeType } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; +import { TokenStyleIdentifier } from 'vs/platform/theme/common/tokenStyleRegistry'; registerColors(); @@ -19,7 +20,8 @@ function getMockTheme(type: ThemeType): ITheme { label: '', type: type, getColor: (colorId: ColorIdentifier): Color | undefined => themingRegistry.resolveDefaultColor(colorId, theme), - defines: () => true + defines: () => true, + getTokenStyle: (tokenStyleId: TokenStyleIdentifier) => undefined }; return theme; } @@ -99,4 +101,4 @@ suite('Workbench - TerminalColorRegistry', () => { '#e5e5e5' ], 'The dark terminal colors should be used when a dark theme is active'); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 6d39ebd157e..fcb5bd87bd5 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -12,7 +12,7 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; import * as resources from 'vs/base/common/resources'; -import { Extensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { Extensions as ColorRegistryExtensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { ThemeType } from 'vs/platform/theme/common/themeService'; import { Registry } from 'vs/platform/registry/common/platform'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; @@ -20,8 +20,12 @@ import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { parse as parsePList } from 'vs/workbench/services/themes/common/plistParser'; import { startsWith } from 'vs/base/common/strings'; +import { Extensions as TokenStyleRegistryExtensions, TokenStyle, TokenStyleIdentifier, ITokenStyleRegistry, TokenStyleBits } from 'vs/platform/theme/common/tokenStyleRegistry'; +import { MatcherWithPriority, Matcher, createMatchers } from 'vs/workbench/services/themes/common/textMateScopeMatcher'; -let colorRegistry = Registry.as(Extensions.ColorContribution); +let colorRegistry = Registry.as(ColorRegistryExtensions.ColorContribution); + +let tokenStyleRegistry = Registry.as(TokenStyleRegistryExtensions.TokenStyleContribution); const tokenGroupToScopesMap = { comments: ['comment'], @@ -33,6 +37,10 @@ const tokenGroupToScopesMap = { variables: ['variable', 'entity.name.variable'] }; +interface ITokenStyleMap { + [id: string]: TokenStyle; +} + export class ColorThemeData implements IColorTheme { id: string; @@ -49,6 +57,11 @@ export class ColorThemeData implements IColorTheme { private colorMap: IColorMap = {}; private customColorMap: IColorMap = {}; + private tokenStyleMap: ITokenStyleMap | undefined; + + private themeTokenScopeMatchers: Matcher[] | undefined; + private customTokenScopeMatchers: Matcher[] | undefined; + private constructor(id: string, label: string, settingsId: string) { this.id = id; this.label = label; @@ -103,10 +116,81 @@ export class ColorThemeData implements IColorTheme { return color; } + public getTokenStyle(tokenStyleId: TokenStyleIdentifier): TokenStyle | undefined { + if (!this.tokenStyleMap) { + this.tokenStyleMap = this.initTokenStyleMap(); + } + let style: TokenStyle | undefined = this.tokenStyleMap[tokenStyleId]; + if (style) { + return style; + } + if (types.isUndefined(style)) { + style = this.getDefaultTokenStyle(style); + } + return style; + } + + private initTokenStyleMap(): ITokenStyleMap { + return {}; + } + public getDefault(colorId: ColorIdentifier): Color | undefined { return colorRegistry.resolveDefaultColor(colorId, this); } + public getDefaultTokenStyle(tokenStyleId: TokenStyleIdentifier): TokenStyle | undefined { + return tokenStyleRegistry.resolveDefaultTokenStyle(tokenStyleId, this, this.findTokenStyleForScope.bind(this)); + } + + /** Public for testing reasons */ + public findTokenStyleForScope(scope: string): TokenStyle | undefined { + + function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITokenColorizationRule[]) { + for (let i = scopeMatchers.length - 1; i >= 0; i--) { + let matcher = scopeMatchers[i]; + if (!matcher) { + scopeMatchers[i] = matcher = getScopeMatcher(tokenColors[i]); + } + if (matcher(scope)) { + const settings = tokenColors[i].settings; + if (settings) { + if (foreground === null && settings.foreground) { + foreground = settings.foreground; + } + if (fontStyle === null && types.isString(settings.fontStyle)) { + fontStyle = settings.fontStyle; + } + if (foreground !== null && fontStyle !== null) { + break; + } + } + } + } + } + + let foreground: string | null = null; + let fontStyle: string | null = null; + + if (!this.customTokenScopeMatchers) { + this.customTokenScopeMatchers = new Array(this.customTokenColors.length); + } + findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors); + + if (foreground === null || fontStyle === null) { + if (!this.themeTokenScopeMatchers) { + this.themeTokenScopeMatchers = new Array(this.themeTokenColors.length); + } + findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors); + } + + if (foreground !== null || fontStyle !== null) { + return getTokenStyle(foreground, fontStyle); + } + return undefined; + } + + + public defines(colorId: ColorIdentifier): boolean { return this.customColorMap.hasOwnProperty(colorId) || this.colorMap.hasOwnProperty(colorId); } @@ -132,6 +216,7 @@ export class ColorThemeData implements IColorTheme { public setCustomTokenColors(customTokenColors: ITokenColorCustomizations) { this.customTokenColors = []; + this.customTokenScopeMatchers = undefined; // first add the non-theme specific settings this.addCustomTokenColors(customTokenColors); @@ -180,6 +265,7 @@ export class ColorThemeData implements IColorTheme { return Promise.resolve(undefined); } this.themeTokenColors = []; + this.themeTokenScopeMatchers = undefined; this.colorMap = {}; return _loadColorTheme(fileService, this.location, this.themeTokenColors, this.colorMap).then(_ => { this.isLoaded = true; @@ -381,4 +467,65 @@ let defaultThemeColors: { [baseTheme: string]: ITokenColorizationRule[] } = { { scope: 'token.error-token', settings: { foreground: '#FF0000' } }, { scope: 'token.debug-token', settings: { foreground: '#b267e6' } } ], -}; \ No newline at end of file +}; + +const noMatch = (_scope: string) => false; + +export function nameMatcher(identifers: string[], scope: string) { + for (const identifier of identifers) { + if (scopesAreMatching(scope, identifier)) { + return true; + } + } + return false; +} + +function scopesAreMatching(thisScopeName: string, scopeName: string): boolean { + if (!thisScopeName) { + return false; + } + if (thisScopeName === scopeName) { + return true; + } + const len = scopeName.length; + return thisScopeName.length > len && thisScopeName.substr(0, len) === scopeName && thisScopeName[len] === '.'; +} + +function getScopeMatcher(rule: ITokenColorizationRule): Matcher { + const scope = rule.scope; + if (!scope || !rule.settings) { + return noMatch; + } + const matchers: MatcherWithPriority[] = []; + if (Array.isArray(scope)) { + for (let s of scope) { + matchers.push(...createMatchers(s, nameMatcher)); + } + } else { + matchers.push(...createMatchers(scope, nameMatcher)); + } + return (scope: string) => matchers.some(m => m.matcher(scope)); +} + +function getTokenStyle(foreground: string | null, fontStyle: string | null): TokenStyle | undefined { + let styles: number | undefined; + let foregroundColor = undefined; + if (foreground !== null) { + foregroundColor = Color.fromHex(foreground); + } + + if (fontStyle !== null) { + styles = 0; + if (fontStyle.indexOf('underline') !== -1) { + styles |= TokenStyleBits.UNDERLINE; + } + if (fontStyle.indexOf('italic') !== -1) { + styles |= TokenStyleBits.ITALIC; + } + if (fontStyle.indexOf('bold') !== -1) { + styles |= TokenStyleBits.BOLD; + } + } + return new TokenStyle(foregroundColor, undefined, styles); + +} diff --git a/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts b/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts new file mode 100644 index 00000000000..16263c9ac89 --- /dev/null +++ b/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts @@ -0,0 +1,111 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +export interface MatcherWithPriority { + matcher: Matcher; + priority: -1 | 0 | 1; +} + +export interface Matcher { + (matcherInput: T): boolean; +} + +export function createMatchers(selector: string, matchesName: (names: string[], matcherInput: T) => boolean): MatcherWithPriority[] { + const results = []>[]; + const tokenizer = newTokenizer(selector); + let token = tokenizer.next(); + while (token !== null) { + let priority: -1 | 0 | 1 = 0; + if (token.length === 2 && token.charAt(1) === ':') { + switch (token.charAt(0)) { + case 'R': priority = 1; break; + case 'L': priority = -1; break; + default: + console.log(`Unknown priority ${token} in scope selector`); + } + token = tokenizer.next(); + } + let matcher = parseConjunction(); + if (matcher) { + results.push({ matcher, priority }); + } + if (token !== ',') { + break; + } + token = tokenizer.next(); + } + return results; + + function parseOperand(): Matcher | null { + if (token === '-') { + token = tokenizer.next(); + const expressionToNegate = parseOperand(); + return matcherInput => !!expressionToNegate && !expressionToNegate(matcherInput); + } + if (token === '(') { + token = tokenizer.next(); + const expressionInParents = parseInnerExpression(); + if (token === ')') { + token = tokenizer.next(); + } + return expressionInParents; + } + if (isIdentifier(token)) { + const identifiers: string[] = []; + do { + identifiers.push(token); + token = tokenizer.next(); + } while (isIdentifier(token)); + return matcherInput => matchesName(identifiers, matcherInput); + } + return null; + } + function parseConjunction(): Matcher { + const matchers: Matcher[] = []; + let matcher = parseOperand(); + while (matcher) { + matchers.push(matcher); + matcher = parseOperand(); + } + return matcherInput => matchers.every(matcher => matcher(matcherInput)); // and + } + function parseInnerExpression(): Matcher { + const matchers: Matcher[] = []; + let matcher = parseConjunction(); + while (matcher) { + matchers.push(matcher); + if (token === '|' || token === ',') { + do { + token = tokenizer.next(); + } while (token === '|' || token === ','); // ignore subsequent commas + } else { + break; + } + matcher = parseConjunction(); + } + return matcherInput => matchers.some(matcher => matcher(matcherInput)); // or + } +} + +function isIdentifier(token: string | null): token is string { + return !!token && !!token.match(/[\w\.:]+/); +} + +function newTokenizer(input: string): { next: () => string | null } { + let regex = /([LR]:|[\w\.:][\w\.:\-]*|[\,\|\-\(\)])/g; + let match = regex.exec(input); + return { + next: () => { + if (!match) { + return null; + } + const res = match[0]; + match = regex.exec(input); + return res; + } + }; +} 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 new file mode 100644 index 00000000000..27979e7eb9c --- /dev/null +++ b/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +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, TokenStyleBits } from 'vs/platform/theme/common/tokenStyleRegistry'; +import { Color } from 'vs/base/common/color'; +import { isString } from 'vs/base/common/types'; + +function ts(foreground: string | undefined, styleFlags: number | undefined): TokenStyle { + const foregroundColor = isString(foreground) ? Color.fromHex(foreground) : undefined; + return new TokenStyle(foregroundColor, undefined, styleFlags); +} + +function tokenStyleAsString(ts: TokenStyle | undefined | null) { + return ts ? `${ts.foreground ? ts.foreground.toString() : 'no-foreground'}-${ts.styles ? ts.styles : 'no-styles'}` : 'tokenstyle-undefined'; +} + +function assertTokenStyle(expected: TokenStyle | undefined | null, actual: TokenStyle | undefined | null, message?: string) { + assert.equal(tokenStyleAsString(expected), tokenStyleAsString(actual), message); +} + + +suite('Themes - TokenStyleResolving', () => { + + // const fileService = new FileService(new NullLogService()); + // const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + // fileService.registerProvider(Schemas.file, diskFileSystemProvider); + + + + + test('resolve resource', async () => { + const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); + + const customTokenColors: ITokenColorCustomizations = { + textMateRules: [ + { + scope: 'variable', + settings: { + fontStyle: '', + foreground: '#F8F8F2' + } + }, + { + scope: 'keyword', + settings: { + fontStyle: 'italic bold underline', + foreground: '#F92672' + } + }, + { + scope: 'storage', + settings: { + fontStyle: 'italic', + foreground: '#F92672' + } + }, + { + scope: 'storage.type', + settings: { + foreground: '#66D9EF' + } + } + ] + }; + + themeData.setCustomTokenColors(customTokenColors); + + let tokenStyle; + + tokenStyle = themeData.findTokenStyleForScope('variable'); + assertTokenStyle(tokenStyle, ts('#F8F8F2', 0), 'variable'); + + tokenStyle = themeData.findTokenStyleForScope('keyword'); + assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC | TokenStyleBits.BOLD | TokenStyleBits.UNDERLINE), 'keyword'); + + tokenStyle = themeData.findTokenStyleForScope('storage'); + assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC), 'storage'); + + tokenStyle = themeData.findTokenStyleForScope('storage.type'); + assertTokenStyle(tokenStyle, ts('#66D9EF', TokenStyleBits.ITALIC), 'storage.type'); + + + }); +}); From fb7cc04c0763af9daf042bc6d34ddb3790a45229 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 13 Sep 2019 17:17:25 +0200 Subject: [PATCH 02/49] more --- .../theme/common/tokenStyleRegistry.ts | 43 ++++++++++----- .../services/themes/common/colorThemeData.ts | 52 +++++++++++-------- .../tokenStyleResolving.test.ts | 24 ++++++--- 3 files changed, 80 insertions(+), 39 deletions(-) diff --git a/src/vs/platform/theme/common/tokenStyleRegistry.ts b/src/vs/platform/theme/common/tokenStyleRegistry.ts index 5f593d41370..5be9d6a650d 100644 --- a/src/vs/platform/theme/common/tokenStyleRegistry.ts +++ b/src/vs/platform/theme/common/tokenStyleRegistry.ts @@ -8,6 +8,7 @@ import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { Color } from 'vs/base/common/color'; import { ITheme } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; +import * as nls from 'vs/nls'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -69,21 +70,21 @@ export namespace TokenStyle { } } - +export type ProbeScope = string[] | string; export interface TokenStyleFunction { (theme: ITheme): TokenStyle | undefined; } export interface TokenStyleDefaults { - scopesToProbe?: string[]; - light: TokenStyle | null; - dark: TokenStyle | null; - hc: TokenStyle | null; + scopesToProbe?: ProbeScope[]; + light: TokenStyleValue | null; + dark: TokenStyleValue | null; + hc: TokenStyleValue | null; } /** - * A TokenStyle Value is either a tokestyle literal, a reference to other color or a derived color + * A TokenStyle Value is either a token style literal, a reference to other token style or a derived token style */ export type TokenStyleValue = TokenStyle | string | TokenStyleIdentifier | TokenStyleFunction; @@ -117,7 +118,7 @@ export interface ITokenStyleRegistry { /** * Gets the default TokenStyle of the given id */ - resolveDefaultTokenStyle(id: TokenStyleIdentifier, theme: ITheme, findTokenStyleForScope: (scope: string) => TokenStyle | undefined): TokenStyle | undefined; + resolveDefaultTokenStyle(id: TokenStyleIdentifier, theme: ITheme, findTokenStyleForScope: (scope: ProbeScope) => TokenStyle | undefined): TokenStyle | undefined; /** * JSON schema for an object to assign TokenStyle values to one of the TokenStyle contributions. @@ -147,9 +148,18 @@ class TokenStyleRegistry implements ITokenStyleRegistry { } public registerTokenStyle(id: string, defaults: TokenStyleDefaults | null, description: string, deprecationMessage?: string): TokenStyleIdentifier { - let colorContribution: TokenStyleContribution = { id, description, defaults, deprecationMessage }; - this.tokenStyleById[id] = colorContribution; - let propertySchema: IJSONSchema = { type: 'string', description, format: 'color-hex', default: '#ff0000' }; + let tokenStyleContribution: TokenStyleContribution = { id, description, defaults, deprecationMessage }; + this.tokenStyleById[id] = tokenStyleContribution; + let propertySchema: IJSONSchema = { + type: 'object', + description, + properties: { + 'foreground': { type: 'string', format: 'color-hex', default: '#ff0000' }, + 'italic': { type: 'boolean' }, + 'bold': { type: 'boolean' }, + 'underline': { type: 'boolean' } + } + }; if (deprecationMessage) { propertySchema.deprecationMessage = deprecationMessage; } @@ -177,7 +187,7 @@ class TokenStyleRegistry implements ITokenStyleRegistry { return Object.keys(this.tokenStyleById).map(id => this.tokenStyleById[id]); } - public resolveDefaultTokenStyle(id: TokenStyleIdentifier, theme: ITheme, findTokenStyleForScope: (scope: string) => TokenStyle | undefined): TokenStyle | undefined { + public resolveDefaultTokenStyle(id: TokenStyleIdentifier, theme: ITheme, findTokenStyleForScope: (scope: ProbeScope) => TokenStyle | undefined): TokenStyle | undefined { const tokenStyleDesc = this.tokenStyleById[id]; if (tokenStyleDesc && tokenStyleDesc.defaults) { const scopesToProbe = tokenStyleDesc.defaults.scopesToProbe; @@ -229,7 +239,16 @@ export function getTokenStyleRegistry(): ITokenStyleRegistry { return tokenStyleRegistry; } -// ----- implementation +// colors + + +export const comments = registerTokenStyle('comments', { scopesToProbe: ['comment'], dark: null, light: null, hc: null }, nls.localize('comments', "Token style for comments.")); +export const strings = registerTokenStyle('strings', { scopesToProbe: ['strings'], dark: null, light: null, hc: null }, nls.localize('strings', "Token style for strings.")); +export const keywords = registerTokenStyle('keywords', { scopesToProbe: ['keyword.control', 'storage', 'storage.type'], dark: null, light: null, hc: null }, nls.localize('keywords', "Token style for keywords.")); +export const numbers = registerTokenStyle('numbers', { scopesToProbe: ['constant.numeric'], dark: null, light: null, hc: null }, nls.localize('numbers', "Token style for numbers.")); +export const types = registerTokenStyle('types', { scopesToProbe: ['entity.name.type', 'entity.name.class', 'support.type', 'support.class'], dark: null, light: null, hc: null }, nls.localize('types', "Token style for types.")); +export const functions = registerTokenStyle('functions', { scopesToProbe: ['entity.name.function', 'support.function'], dark: null, light: null, hc: null }, nls.localize('functions', "Token style for functions.")); +export const variables = registerTokenStyle('variables', { scopesToProbe: ['variable', 'entity.name.variable'], dark: null, light: null, hc: null }, nls.localize('variables', "Token style for variables.")); /** * @param colorValue Resolve a color value in the context of a theme diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index fcb5bd87bd5..68281aaa572 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -20,7 +20,7 @@ import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { parse as parsePList } from 'vs/workbench/services/themes/common/plistParser'; import { startsWith } from 'vs/base/common/strings'; -import { Extensions as TokenStyleRegistryExtensions, TokenStyle, TokenStyleIdentifier, ITokenStyleRegistry, TokenStyleBits } from 'vs/platform/theme/common/tokenStyleRegistry'; +import { Extensions as TokenStyleRegistryExtensions, TokenStyle, TokenStyleIdentifier, ITokenStyleRegistry, TokenStyleBits, ProbeScope } from 'vs/platform/theme/common/tokenStyleRegistry'; import { MatcherWithPriority, Matcher, createMatchers } from 'vs/workbench/services/themes/common/textMateScopeMatcher'; let colorRegistry = Registry.as(ColorRegistryExtensions.ColorContribution); @@ -59,8 +59,8 @@ export class ColorThemeData implements IColorTheme { private tokenStyleMap: ITokenStyleMap | undefined; - private themeTokenScopeMatchers: Matcher[] | undefined; - private customTokenScopeMatchers: Matcher[] | undefined; + private themeTokenScopeMatchers: Matcher[] | undefined; + private customTokenScopeMatchers: Matcher[] | undefined; private constructor(id: string, label: string, settingsId: string) { this.id = id; @@ -143,9 +143,9 @@ export class ColorThemeData implements IColorTheme { } /** Public for testing reasons */ - public findTokenStyleForScope(scope: string): TokenStyle | undefined { + public findTokenStyleForScope(scope: ProbeScope): TokenStyle | undefined { - function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITokenColorizationRule[]) { + function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITokenColorizationRule[]) { for (let i = scopeMatchers.length - 1; i >= 0; i--) { let matcher = scopeMatchers[i]; if (!matcher) { @@ -469,15 +469,25 @@ let defaultThemeColors: { [baseTheme: string]: ITokenColorizationRule[] } = { ], }; -const noMatch = (_scope: string) => false; +const noMatch = (_scope: ProbeScope) => false; -export function nameMatcher(identifers: string[], scope: string) { - for (const identifier of identifers) { - if (scopesAreMatching(scope, identifier)) { - return true; - } +function nameMatcher(identifers: string[], scope: ProbeScope) { + if (!Array.isArray(scope)) { + scope = [scope]; } - return false; + if (scope.length < identifers.length) { + return false; + } + let lastIndex = 0; + return identifers.every(identifier => { + for (let i = lastIndex; i < scope.length; i++) { + if (scopesAreMatching(scope[i], identifier)) { + lastIndex = i + 1; + return true; + } + } + return false; + }); } function scopesAreMatching(thisScopeName: string, scopeName: string): boolean { @@ -491,20 +501,20 @@ function scopesAreMatching(thisScopeName: string, scopeName: string): boolean { return thisScopeName.length > len && thisScopeName.substr(0, len) === scopeName && thisScopeName[len] === '.'; } -function getScopeMatcher(rule: ITokenColorizationRule): Matcher { - const scope = rule.scope; - if (!scope || !rule.settings) { +function getScopeMatcher(rule: ITokenColorizationRule): Matcher { + const ruleScope = rule.scope; + if (!ruleScope || !rule.settings) { return noMatch; } - const matchers: MatcherWithPriority[] = []; - if (Array.isArray(scope)) { - for (let s of scope) { - matchers.push(...createMatchers(s, nameMatcher)); + const matchers: MatcherWithPriority[] = []; + if (Array.isArray(ruleScope)) { + for (let rs of ruleScope) { + matchers.push(...createMatchers(rs, nameMatcher)); } } else { - matchers.push(...createMatchers(scope, nameMatcher)); + matchers.push(...createMatchers(ruleScope, nameMatcher)); } - return (scope: string) => matchers.some(m => m.matcher(scope)); + return (scope: ProbeScope) => matchers.some(m => m.matcher(scope)); } function getTokenStyle(foreground: string | null, fontStyle: string | null): TokenStyle | undefined { 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 27979e7eb9c..c8c3ef86ccf 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 @@ -60,11 +60,18 @@ suite('Themes - TokenStyleResolving', () => { } }, { - scope: 'storage.type', + scope: ['storage.type', 'meta.structure.dictionary.json string.quoted.double.json'], settings: { foreground: '#66D9EF' } - } + }, + { + scope: 'entity.name.type, entity.name.class, entity.name.namespace, entity.name.scope-resolution', + settings: { + fontStyle: 'underline', + foreground: '#A6E22E' + } + }, ] }; @@ -72,18 +79,23 @@ suite('Themes - TokenStyleResolving', () => { let tokenStyle; - tokenStyle = themeData.findTokenStyleForScope('variable'); + tokenStyle = themeData.findTokenStyleForScope(['variable']); assertTokenStyle(tokenStyle, ts('#F8F8F2', 0), 'variable'); - tokenStyle = themeData.findTokenStyleForScope('keyword'); + tokenStyle = themeData.findTokenStyleForScope(['keyword']); assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC | TokenStyleBits.BOLD | TokenStyleBits.UNDERLINE), 'keyword'); - tokenStyle = themeData.findTokenStyleForScope('storage'); + tokenStyle = themeData.findTokenStyleForScope(['storage']); assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC), 'storage'); - tokenStyle = themeData.findTokenStyleForScope('storage.type'); + tokenStyle = themeData.findTokenStyleForScope(['storage.type']); assertTokenStyle(tokenStyle, ts('#66D9EF', TokenStyleBits.ITALIC), 'storage.type'); + tokenStyle = themeData.findTokenStyleForScope(['entity.name.class']); + assertTokenStyle(tokenStyle, ts('#A6E22E', TokenStyleBits.UNDERLINE), 'entity.name.class'); + + tokenStyle = themeData.findTokenStyleForScope(['meta.structure.dictionary.json', 'string.quoted.double.json']); + assertTokenStyle(tokenStyle, ts('#66D9EF', undefined), 'json property'); }); }); From f337499eb451e84d57a60a9ee9470c09e88d0f2d Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 18 Sep 2019 13:46:56 +0200 Subject: [PATCH 03/49] monokai test --- .../theme/common/tokenStyleRegistry.ts | 2 +- .../services/themes/common/colorThemeData.ts | 15 ++---- .../tokenStyleResolving.test.ts | 47 +++++++++++++++++-- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/vs/platform/theme/common/tokenStyleRegistry.ts b/src/vs/platform/theme/common/tokenStyleRegistry.ts index 5be9d6a650d..1dbd13ff778 100644 --- a/src/vs/platform/theme/common/tokenStyleRegistry.ts +++ b/src/vs/platform/theme/common/tokenStyleRegistry.ts @@ -243,7 +243,7 @@ export function getTokenStyleRegistry(): ITokenStyleRegistry { export const comments = registerTokenStyle('comments', { scopesToProbe: ['comment'], dark: null, light: null, hc: null }, nls.localize('comments', "Token style for comments.")); -export const strings = registerTokenStyle('strings', { scopesToProbe: ['strings'], dark: null, light: null, hc: null }, nls.localize('strings', "Token style for strings.")); +export const strings = registerTokenStyle('strings', { scopesToProbe: ['string'], dark: null, light: null, hc: null }, nls.localize('strings', "Token style for strings.")); export const keywords = registerTokenStyle('keywords', { scopesToProbe: ['keyword.control', 'storage', 'storage.type'], dark: null, light: null, hc: null }, nls.localize('keywords', "Token style for keywords.")); export const numbers = registerTokenStyle('numbers', { scopesToProbe: ['constant.numeric'], dark: null, light: null, hc: null }, nls.localize('numbers', "Token style for numbers.")); export const types = registerTokenStyle('types', { scopesToProbe: ['entity.name.type', 'entity.name.class', 'support.type', 'support.class'], dark: null, light: null, hc: null }, nls.localize('types', "Token style for types.")); diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 68281aaa572..9d2f319a343 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -57,7 +57,7 @@ export class ColorThemeData implements IColorTheme { private colorMap: IColorMap = {}; private customColorMap: IColorMap = {}; - private tokenStyleMap: ITokenStyleMap | undefined; + private tokenStyleMap: ITokenStyleMap = {}; private themeTokenScopeMatchers: Matcher[] | undefined; private customTokenScopeMatchers: Matcher[] | undefined; @@ -116,24 +116,17 @@ export class ColorThemeData implements IColorTheme { return color; } - public getTokenStyle(tokenStyleId: TokenStyleIdentifier): TokenStyle | undefined { - if (!this.tokenStyleMap) { - this.tokenStyleMap = this.initTokenStyleMap(); - } + public getTokenStyle(tokenStyleId: TokenStyleIdentifier, useDefault?: boolean): TokenStyle | undefined { let style: TokenStyle | undefined = this.tokenStyleMap[tokenStyleId]; if (style) { return style; } - if (types.isUndefined(style)) { - style = this.getDefaultTokenStyle(style); + if (useDefault !== false && types.isUndefined(style)) { + style = this.getDefaultTokenStyle(tokenStyleId); } return style; } - private initTokenStyleMap(): ITokenStyleMap { - return {}; - } - public getDefault(colorId: ColorIdentifier): Color | undefined { return colorRegistry.resolveDefaultColor(colorId, this); } 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 c8c3ef86ccf..adcb8dcd749 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,9 +6,15 @@ 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, TokenStyleBits } from 'vs/platform/theme/common/tokenStyleRegistry'; +import { TokenStyle, TokenStyleBits, comments, variables, types, functions, keywords, numbers, strings } from 'vs/platform/theme/common/tokenStyleRegistry'; import { Color } from 'vs/base/common/color'; import { isString } from 'vs/base/common/types'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; +import { Schemas } from 'vs/base/common/network'; +import { URI } from 'vs/base/common/uri'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; function ts(foreground: string | undefined, styleFlags: number | undefined): TokenStyle { const foregroundColor = isString(foreground) ? Color.fromHex(foreground) : undefined; @@ -24,14 +30,45 @@ function assertTokenStyle(expected: TokenStyle | undefined | null, actual: Token } + suite('Themes - TokenStyleResolving', () => { - - // const fileService = new FileService(new NullLogService()); - // const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); - // fileService.registerProvider(Schemas.file, diskFileSystemProvider); + const fileService = new FileService(new NullLogService()); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); + fileService.registerProvider(Schemas.file, diskFileSystemProvider); + test('color defaults - monokai', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-monokai/themes/monokai-color-theme.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(fileService); + assert.equal(themeData.isLoaded, true); + + let tokenStyle; + + tokenStyle = themeData.getTokenStyle(comments); + assertTokenStyle(tokenStyle, ts('#75715E', 0)); + + tokenStyle = themeData.getTokenStyle(variables); + assertTokenStyle(tokenStyle, ts('#F8F8F2', 0)); + + tokenStyle = themeData.getTokenStyle(types); + assertTokenStyle(tokenStyle, ts('#A6E22E', TokenStyleBits.UNDERLINE)); + + tokenStyle = themeData.getTokenStyle(functions); + assertTokenStyle(tokenStyle, ts('#A6E22E', 0)); + + tokenStyle = themeData.getTokenStyle(strings); + assertTokenStyle(tokenStyle, ts('#E6DB74', 0)); + + tokenStyle = themeData.getTokenStyle(numbers); + assertTokenStyle(tokenStyle, ts('#AE81FF', 0)); + + tokenStyle = themeData.getTokenStyle(keywords); + assertTokenStyle(tokenStyle, ts('#F92672', 0)); + + }); test('resolve resource', async () => { const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); From fa99aa5c34816ac20fa4c71652cf2b0fc477e99c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 18 Sep 2019 23:16:52 +0200 Subject: [PATCH 04/49] more tests --- .../theme/common/tokenStyleRegistry.ts | 4 + .../tokenStyleResolving.test.ts | 141 +++++++++++++++--- 2 files changed, 126 insertions(+), 19 deletions(-) diff --git a/src/vs/platform/theme/common/tokenStyleRegistry.ts b/src/vs/platform/theme/common/tokenStyleRegistry.ts index 1dbd13ff778..e212367d873 100644 --- a/src/vs/platform/theme/common/tokenStyleRegistry.ts +++ b/src/vs/platform/theme/common/tokenStyleRegistry.ts @@ -12,6 +12,7 @@ import * as nls from 'vs/nls'; import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { RunOnceScheduler } from 'vs/base/common/async'; +import { editorForeground } from 'vs/platform/theme/common/colorRegistry'; // ------ API types @@ -200,6 +201,9 @@ class TokenStyleRegistry implements ITokenStyleRegistry { } } const tokenStyleValue = tokenStyleDesc.defaults[theme.type]; + if (tokenStyleValue === null) { + return new TokenStyle(theme.getColor(editorForeground)); + } return resolveTokenStyleValue(tokenStyleValue, theme); } return undefined; 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 adcb8dcd749..ac70d635999 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 @@ -25,8 +25,15 @@ function tokenStyleAsString(ts: TokenStyle | undefined | null) { return ts ? `${ts.foreground ? ts.foreground.toString() : 'no-foreground'}-${ts.styles ? ts.styles : 'no-styles'}` : 'tokenstyle-undefined'; } -function assertTokenStyle(expected: TokenStyle | undefined | null, actual: TokenStyle | undefined | null, message?: string) { - assert.equal(tokenStyleAsString(expected), tokenStyleAsString(actual), message); +function assertTokenStyle(actual: TokenStyle | undefined | null, expected: TokenStyle | undefined | null, message?: string) { + assert.equal(tokenStyleAsString(actual), tokenStyleAsString(expected), message); +} + +function assertTokenStyles(themeData: ColorThemeData, expected: { [tokenStyleId: string]: TokenStyle }) { + for (let tokenStyleId in expected) { + const tokenStyle = themeData.getTokenStyle(tokenStyleId); + assertTokenStyle(tokenStyle, expected[tokenStyleId], tokenStyleId); + } } @@ -45,28 +52,115 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); - let tokenStyle; + assertTokenStyles(themeData, { + [comments]: ts('#75715E', 0), + [variables]: ts('#F8F8F2', 0), + [types]: ts('#A6E22E', TokenStyleBits.UNDERLINE), + [functions]: ts('#A6E22E', 0), + [strings]: ts('#E6DB74', 0), + [numbers]: ts('#AE81FF', 0), + [keywords]: ts('#F92672', 0) + }); - tokenStyle = themeData.getTokenStyle(comments); - assertTokenStyle(tokenStyle, ts('#75715E', 0)); + }); - tokenStyle = themeData.getTokenStyle(variables); - assertTokenStyle(tokenStyle, ts('#F8F8F2', 0)); + test('color defaults - dark+', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/dark_plus.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(fileService); - tokenStyle = themeData.getTokenStyle(types); - assertTokenStyle(tokenStyle, ts('#A6E22E', TokenStyleBits.UNDERLINE)); + assert.equal(themeData.isLoaded, true); - tokenStyle = themeData.getTokenStyle(functions); - assertTokenStyle(tokenStyle, ts('#A6E22E', 0)); + assertTokenStyles(themeData, { + [comments]: ts('#6A9955', 0), + [variables]: ts('#9CDCFE', 0), + [types]: ts('#4EC9B0', 0), + [functions]: ts('#DCDCAA', 0), + [strings]: ts('#CE9178', 0), + [numbers]: ts('#B5CEA8', 0), + [keywords]: ts('#C586C0', 0) + }); - tokenStyle = themeData.getTokenStyle(strings); - assertTokenStyle(tokenStyle, ts('#E6DB74', 0)); + }); - tokenStyle = themeData.getTokenStyle(numbers); - assertTokenStyle(tokenStyle, ts('#AE81FF', 0)); + test('color defaults - light vs', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/light_vs.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(fileService); - tokenStyle = themeData.getTokenStyle(keywords); - assertTokenStyle(tokenStyle, ts('#F92672', 0)); + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#008000', 0), + [variables]: ts('#000000', 0), + [types]: ts('#000000', 0), + [functions]: ts('#000000', 0), + [strings]: ts('#a31515', 0), + [numbers]: ts('#09885a', 0), + [keywords]: ts('#0000ff', 0) + }); + + }); + + test('color defaults - hc', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/hc_black.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(fileService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#7ca668', 0), + [variables]: ts('#9CDCFE', 0), + [types]: ts('#4EC9B0', 0), + [functions]: ts('#DCDCAA', 0), + [strings]: ts('#ce9178', 0), + [numbers]: ts('#b5cea8', 0), + [keywords]: ts('#C586C0', 0) + }); + + }); + + test('color defaults - kimbie dark', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(fileService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#a57a4c', 0), + [variables]: ts('#dc3958', 0), + [types]: ts('#f06431', 0), + [functions]: ts('#8ab1b0', 0), + [strings]: ts('#889b4a', 0), + [numbers]: ts('#f79a32', 0), + [keywords]: ts('#98676a', 0) + }); + + }); + + test('color defaults - abyss', async () => { + const themeData = ColorThemeData.createUnloadedTheme('foo'); + const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-abyss/themes/abyss-color-theme.json'); + themeData.location = URI.file(themeLocation); + await themeData.ensureLoaded(fileService); + + assert.equal(themeData.isLoaded, true); + + assertTokenStyles(themeData, { + [comments]: ts('#384887', 0), + [variables]: ts('#9966b8', 0), + [types]: ts('#f06431', 0), + [functions]: ts('#8ab1b0', 0), + [strings]: ts('#22aa44', 0), + [numbers]: ts('#f280d0', 0), + [keywords]: ts('#98676a', 0) + }); }); @@ -83,7 +177,7 @@ suite('Themes - TokenStyleResolving', () => { } }, { - scope: 'keyword', + scope: 'keyword.operator', settings: { fontStyle: 'italic bold underline', foreground: '#F92672' @@ -119,9 +213,18 @@ suite('Themes - TokenStyleResolving', () => { tokenStyle = themeData.findTokenStyleForScope(['variable']); assertTokenStyle(tokenStyle, ts('#F8F8F2', 0), 'variable'); - tokenStyle = themeData.findTokenStyleForScope(['keyword']); + tokenStyle = themeData.findTokenStyleForScope(['keyword.operator']); assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC | TokenStyleBits.BOLD | TokenStyleBits.UNDERLINE), 'keyword'); + tokenStyle = themeData.findTokenStyleForScope(['keyword']); + assertTokenStyle(tokenStyle, undefined, 'keyword'); + + tokenStyle = themeData.findTokenStyleForScope(['keyword.operator']); + assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC | TokenStyleBits.BOLD | TokenStyleBits.UNDERLINE), 'keyword.operator'); + + tokenStyle = themeData.findTokenStyleForScope(['keyword.operators']); + assertTokenStyle(tokenStyle, undefined, 'keyword.operators'); + tokenStyle = themeData.findTokenStyleForScope(['storage']); assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC), 'storage'); From 4a1614f80b78c92cf446d98e942846dbeacdf1de Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 Sep 2019 09:13:12 +0200 Subject: [PATCH 05/49] match score --- .../services/themes/common/colorThemeData.ts | 99 +++++++++++-------- .../themes/common/textMateScopeMatcher.ts | 43 ++++++-- 2 files changed, 93 insertions(+), 49 deletions(-) diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 9d2f319a343..12079e669db 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -138,44 +138,41 @@ export class ColorThemeData implements IColorTheme { /** Public for testing reasons */ public findTokenStyleForScope(scope: ProbeScope): TokenStyle | undefined { + let foreground: string | null = null; + let fontStyle: string | null = null; + let foregroundScore = -1; + let fontStyleScore = -1; + function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITokenColorizationRule[]) { - for (let i = scopeMatchers.length - 1; i >= 0; i--) { + for (let i = 0; i < scopeMatchers.length; i++) { + const settings = tokenColors[i].settings; + if (!settings) { + continue; + } let matcher = scopeMatchers[i]; if (!matcher) { scopeMatchers[i] = matcher = getScopeMatcher(tokenColors[i]); } - if (matcher(scope)) { - const settings = tokenColors[i].settings; - if (settings) { - if (foreground === null && settings.foreground) { - foreground = settings.foreground; - } - if (fontStyle === null && types.isString(settings.fontStyle)) { - fontStyle = settings.fontStyle; - } - if (foreground !== null && fontStyle !== null) { - break; - } - } + const score = matcher(scope); + if (score > foregroundScore && settings.foreground) { + foreground = settings.foreground; + } + if (score > fontStyleScore && types.isString(settings.fontStyle)) { + fontStyle = settings.fontStyle; } } } - let foreground: string | null = null; - let fontStyle: string | null = null; + if (!this.themeTokenScopeMatchers) { + this.themeTokenScopeMatchers = new Array(this.themeTokenColors.length); + } + findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors); if (!this.customTokenScopeMatchers) { this.customTokenScopeMatchers = new Array(this.customTokenColors.length); } findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors); - if (foreground === null || fontStyle === null) { - if (!this.themeTokenScopeMatchers) { - this.themeTokenScopeMatchers = new Array(this.themeTokenColors.length); - } - findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors); - } - if (foreground !== null || fontStyle !== null) { return getTokenStyle(foreground, fontStyle); } @@ -462,27 +459,43 @@ let defaultThemeColors: { [baseTheme: string]: ITokenColorizationRule[] } = { ], }; -const noMatch = (_scope: ProbeScope) => false; +const noMatch = (_scope: ProbeScope) => -1; -function nameMatcher(identifers: string[], scope: ProbeScope) { - if (!Array.isArray(scope)) { - scope = [scope]; - } - if (scope.length < identifers.length) { - return false; - } - let lastIndex = 0; - return identifers.every(identifier => { - for (let i = lastIndex; i < scope.length; i++) { - if (scopesAreMatching(scope[i], identifier)) { - lastIndex = i + 1; - return true; +function nameMatcher(identifers: string[], scope: ProbeScope): number { + function findInIdents(s: string, lastIndent: number): number { + for (let i = lastIndent - 1; i >= 0; i--) { + if (scopesAreMatching(identifers[i], s)) { + return i; } } - return false; - }); + return -1; + } + if (!Array.isArray(scope)) { + const idx = findInIdents(scope, identifers.length); + if (idx >= 0) { + return idx * 0x10000 + scope.length; + } + return -1; + } + if (scope.length < identifers.length) { + return -1; + } + let lastScopeIndex = scope.length - 1; + let lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], identifers.length); + if (lastIdentifierIndex >= 0) { + const score = lastIdentifierIndex * 0x10000 + scope.length; + while (lastScopeIndex >= 0) { + lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], lastIdentifierIndex); + if (lastIdentifierIndex === -1) { + return -1; + } + } + return score; + } + return -1; } + function scopesAreMatching(thisScopeName: string, scopeName: string): boolean { if (!thisScopeName) { return false; @@ -507,7 +520,13 @@ function getScopeMatcher(rule: ITokenColorizationRule): Matcher { } else { matchers.push(...createMatchers(ruleScope, nameMatcher)); } - return (scope: ProbeScope) => matchers.some(m => m.matcher(scope)); + return (scope: ProbeScope) => { + let max = 0; + for (const m of matchers) { + max = Math.max(max, m.matcher(scope)); + } + return max; + }; } function getTokenStyle(foreground: string | null, fontStyle: string | null): TokenStyle | undefined { diff --git a/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts b/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts index 16263c9ac89..701081bca4c 100644 --- a/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts +++ b/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts @@ -11,10 +11,10 @@ export interface MatcherWithPriority { } export interface Matcher { - (matcherInput: T): boolean; + (matcherInput: T): number; } -export function createMatchers(selector: string, matchesName: (names: string[], matcherInput: T) => boolean): MatcherWithPriority[] { +export function createMatchers(selector: string, matchesName: (names: string[], matcherInput: T) => number): MatcherWithPriority[] { const results = []>[]; const tokenizer = newTokenizer(selector); let token = tokenizer.next(); @@ -44,7 +44,13 @@ export function createMatchers(selector: string, matchesName: (names: string[ if (token === '-') { token = tokenizer.next(); const expressionToNegate = parseOperand(); - return matcherInput => !!expressionToNegate && !expressionToNegate(matcherInput); + if (!expressionToNegate) { + return null; + } + return matcherInput => { + const score = expressionToNegate(matcherInput); + return score < 0 ? 0 : -1; + }; } if (token === '(') { token = tokenizer.next(); @@ -64,18 +70,31 @@ export function createMatchers(selector: string, matchesName: (names: string[ } return null; } - function parseConjunction(): Matcher { - const matchers: Matcher[] = []; + function parseConjunction(): Matcher | null { let matcher = parseOperand(); + if (!matcher) { + return null; + } + + const matchers: Matcher[] = []; while (matcher) { matchers.push(matcher); matcher = parseOperand(); } - return matcherInput => matchers.every(matcher => matcher(matcherInput)); // and + return matcherInput => { // and + let min = matchers[0](matcherInput); + for (let i = 1; min >= 0 && i < matchers.length; i++) { + min = Math.min(min, matchers[i](matcherInput)); + } + return min; + }; } - function parseInnerExpression(): Matcher { - const matchers: Matcher[] = []; + function parseInnerExpression(): Matcher | null { let matcher = parseConjunction(); + if (!matcher) { + return null; + } + const matchers: Matcher[] = []; while (matcher) { matchers.push(matcher); if (token === '|' || token === ',') { @@ -87,7 +106,13 @@ export function createMatchers(selector: string, matchesName: (names: string[ } matcher = parseConjunction(); } - return matcherInput => matchers.some(matcher => matcher(matcherInput)); // or + return matcherInput => { // or + let max = matchers[0](matcherInput); + for (let i = 1; i < matchers.length; i++) { + max = Math.max(max, matchers[i](matcherInput)); + } + return max; + }; } } From 6dce52093c8a2fd178f86e4de8d4844a89c33297 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 Sep 2019 16:11:43 +0200 Subject: [PATCH 06/49] fix score based rule matching --- .../theme-abyss/themes/abyss-color-theme.json | 11 ++--- .../theme/common/tokenStyleRegistry.ts | 16 +++---- .../services/themes/common/colorThemeData.ts | 42 +++++++++---------- .../tokenStyleResolving.test.ts | 14 ++++--- 4 files changed, 39 insertions(+), 44 deletions(-) diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json index 18c8235f79c..775dc8614d0 100644 --- a/extensions/theme-abyss/themes/abyss-color-theme.json +++ b/extensions/theme-abyss/themes/abyss-color-theme.json @@ -1,12 +1,6 @@ { "name": "Abyss", "tokenColors": [ - { - "settings": { - "background": "#000c18", - "foreground": "#6688cc" - } - }, { "scope": ["meta.embedded", "source.groovy.embedded"], "settings": { @@ -260,6 +254,9 @@ ], "colors": { + "editor.background": "#000c18", + "editor.foreground": "#6688cc", + // Base // "foreground": "", "focusBorder": "#596F99", @@ -303,8 +300,6 @@ "scrollbarSlider.hoverBackground": "#3B3F5188", // Editor - "editor.background": "#000c18", - // "editor.foreground": "#6688cc", "editorWidget.background": "#262641", "editorCursor.foreground": "#ddbb88", "editorWhitespace.foreground": "#103050", diff --git a/src/vs/platform/theme/common/tokenStyleRegistry.ts b/src/vs/platform/theme/common/tokenStyleRegistry.ts index e212367d873..27f1a7ebc29 100644 --- a/src/vs/platform/theme/common/tokenStyleRegistry.ts +++ b/src/vs/platform/theme/common/tokenStyleRegistry.ts @@ -71,7 +71,7 @@ export namespace TokenStyle { } } -export type ProbeScope = string[] | string; +export type ProbeScope = string[]; export interface TokenStyleFunction { (theme: ITheme): TokenStyle | undefined; @@ -246,13 +246,13 @@ export function getTokenStyleRegistry(): ITokenStyleRegistry { // colors -export const comments = registerTokenStyle('comments', { scopesToProbe: ['comment'], dark: null, light: null, hc: null }, nls.localize('comments', "Token style for comments.")); -export const strings = registerTokenStyle('strings', { scopesToProbe: ['string'], dark: null, light: null, hc: null }, nls.localize('strings', "Token style for strings.")); -export const keywords = registerTokenStyle('keywords', { scopesToProbe: ['keyword.control', 'storage', 'storage.type'], dark: null, light: null, hc: null }, nls.localize('keywords', "Token style for keywords.")); -export const numbers = registerTokenStyle('numbers', { scopesToProbe: ['constant.numeric'], dark: null, light: null, hc: null }, nls.localize('numbers', "Token style for numbers.")); -export const types = registerTokenStyle('types', { scopesToProbe: ['entity.name.type', 'entity.name.class', 'support.type', 'support.class'], dark: null, light: null, hc: null }, nls.localize('types', "Token style for types.")); -export const functions = registerTokenStyle('functions', { scopesToProbe: ['entity.name.function', 'support.function'], dark: null, light: null, hc: null }, nls.localize('functions', "Token style for functions.")); -export const variables = registerTokenStyle('variables', { scopesToProbe: ['variable', 'entity.name.variable'], dark: null, light: null, hc: null }, nls.localize('variables', "Token style for variables.")); +export const comments = registerTokenStyle('comments', { scopesToProbe: [['comment']], dark: null, light: null, hc: null }, nls.localize('comments', "Token style for comments.")); +export const strings = registerTokenStyle('strings', { scopesToProbe: [['string']], dark: null, light: null, hc: null }, nls.localize('strings', "Token style for strings.")); +export const keywords = registerTokenStyle('keywords', { scopesToProbe: [['keyword.control'], ['storage'], ['storage.type']], dark: null, light: null, hc: null }, nls.localize('keywords', "Token style for keywords.")); +export const numbers = registerTokenStyle('numbers', { scopesToProbe: [['constant.numeric']], dark: null, light: null, hc: null }, nls.localize('numbers', "Token style for numbers.")); +export const types = registerTokenStyle('types', { scopesToProbe: [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']], dark: null, light: null, hc: null }, nls.localize('types', "Token style for types.")); +export const functions = registerTokenStyle('functions', { scopesToProbe: [['entity.name.function'], ['support.function']], dark: null, light: null, hc: null }, nls.localize('functions', "Token style for functions.")); +export const variables = registerTokenStyle('variables', { scopesToProbe: [['variable'], ['entity.name.variable']], dark: null, light: null, hc: null }, nls.localize('variables', "Token style for variables.")); /** * @param colorValue Resolve a color value in the context of a theme diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 12079e669db..4dbdfcc2eff 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -138,7 +138,9 @@ export class ColorThemeData implements IColorTheme { /** Public for testing reasons */ public findTokenStyleForScope(scope: ProbeScope): TokenStyle | undefined { - let foreground: string | null = null; + let foregroundColor = this.getColor(editorForeground); + + let foreground = foregroundColor ? foregroundColor.toString() : null; let fontStyle: string | null = null; let foregroundScore = -1; let fontStyleScore = -1; @@ -154,12 +156,15 @@ export class ColorThemeData implements IColorTheme { scopeMatchers[i] = matcher = getScopeMatcher(tokenColors[i]); } const score = matcher(scope); - if (score > foregroundScore && settings.foreground) { - foreground = settings.foreground; - } - if (score > fontStyleScore && types.isString(settings.fontStyle)) { - fontStyle = settings.fontStyle; + if (score >= 0) { + if (score >= foregroundScore && settings.foreground) { + foreground = settings.foreground; + } + if (score >= fontStyleScore && types.isString(settings.fontStyle)) { + fontStyle = settings.fontStyle; + } } + } } @@ -173,10 +178,7 @@ export class ColorThemeData implements IColorTheme { } findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors); - if (foreground !== null || fontStyle !== null) { - return getTokenStyle(foreground, fontStyle); - } - return undefined; + return getTokenStyle(foreground, fontStyle); } @@ -464,26 +466,19 @@ const noMatch = (_scope: ProbeScope) => -1; function nameMatcher(identifers: string[], scope: ProbeScope): number { function findInIdents(s: string, lastIndent: number): number { for (let i = lastIndent - 1; i >= 0; i--) { - if (scopesAreMatching(identifers[i], s)) { + if (scopesAreMatching(s, identifers[i])) { return i; } } return -1; } - if (!Array.isArray(scope)) { - const idx = findInIdents(scope, identifers.length); - if (idx >= 0) { - return idx * 0x10000 + scope.length; - } - return -1; - } if (scope.length < identifers.length) { return -1; } let lastScopeIndex = scope.length - 1; let lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], identifers.length); if (lastIdentifierIndex >= 0) { - const score = lastIdentifierIndex * 0x10000 + scope.length; + const score = (lastIdentifierIndex + 1) * 0x10000 + scope.length; while (lastScopeIndex >= 0) { lastIdentifierIndex = findInIdents(scope[lastScopeIndex--], lastIdentifierIndex); if (lastIdentifierIndex === -1) { @@ -520,10 +515,13 @@ function getScopeMatcher(rule: ITokenColorizationRule): Matcher { } else { matchers.push(...createMatchers(ruleScope, nameMatcher)); } + if (matchers.length === 0) { + return noMatch; + } return (scope: ProbeScope) => { - let max = 0; - for (const m of matchers) { - max = Math.max(max, m.matcher(scope)); + let max = matchers[0].matcher(scope); + for (let i = 1; i < matchers.length; i++) { + max = Math.max(max, matchers[i].matcher(scope)); } return max; }; 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 ac70d635999..12b9e9102da 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 @@ -15,6 +15,7 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { editorForeground } from 'vs/platform/theme/common/colorRegistry'; function ts(foreground: string | undefined, styleFlags: number | undefined): TokenStyle { const foregroundColor = isString(foreground) ? Color.fromHex(foreground) : undefined; @@ -154,12 +155,12 @@ suite('Themes - TokenStyleResolving', () => { assertTokenStyles(themeData, { [comments]: ts('#384887', 0), - [variables]: ts('#9966b8', 0), - [types]: ts('#f06431', 0), - [functions]: ts('#8ab1b0', 0), + [variables]: ts('#6688cc', 0), + [types]: ts('#ffeebb', TokenStyleBits.UNDERLINE), + [functions]: ts('#ddbb88', 0), [strings]: ts('#22aa44', 0), [numbers]: ts('#f280d0', 0), - [keywords]: ts('#98676a', 0) + [keywords]: ts('#225588', 0) }); }); @@ -209,6 +210,7 @@ suite('Themes - TokenStyleResolving', () => { themeData.setCustomTokenColors(customTokenColors); let tokenStyle; + let defaultTokenStyle = new TokenStyle(themeData.getColor(editorForeground)); tokenStyle = themeData.findTokenStyleForScope(['variable']); assertTokenStyle(tokenStyle, ts('#F8F8F2', 0), 'variable'); @@ -217,13 +219,13 @@ suite('Themes - TokenStyleResolving', () => { assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC | TokenStyleBits.BOLD | TokenStyleBits.UNDERLINE), 'keyword'); tokenStyle = themeData.findTokenStyleForScope(['keyword']); - assertTokenStyle(tokenStyle, undefined, 'keyword'); + assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword'); tokenStyle = themeData.findTokenStyleForScope(['keyword.operator']); assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC | TokenStyleBits.BOLD | TokenStyleBits.UNDERLINE), 'keyword.operator'); tokenStyle = themeData.findTokenStyleForScope(['keyword.operators']); - assertTokenStyle(tokenStyle, undefined, 'keyword.operators'); + assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword.operators'); tokenStyle = themeData.findTokenStyleForScope(['storage']); assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC), 'storage'); From 3bc026d9b05346bdc126db6aa67cefce3158f735 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 19 Sep 2019 17:16:30 +0200 Subject: [PATCH 07/49] classifictions --- .../theme/common/tokenStyleRegistry.ts | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/vs/platform/theme/common/tokenStyleRegistry.ts b/src/vs/platform/theme/common/tokenStyleRegistry.ts index 27f1a7ebc29..45f548b3520 100644 --- a/src/vs/platform/theme/common/tokenStyleRegistry.ts +++ b/src/vs/platform/theme/common/tokenStyleRegistry.ts @@ -253,7 +253,43 @@ export const numbers = registerTokenStyle('numbers', { scopesToProbe: [['constan export const types = registerTokenStyle('types', { scopesToProbe: [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']], dark: null, light: null, hc: null }, nls.localize('types', "Token style for types.")); export const functions = registerTokenStyle('functions', { scopesToProbe: [['entity.name.function'], ['support.function']], dark: null, light: null, hc: null }, nls.localize('functions', "Token style for functions.")); export const variables = registerTokenStyle('variables', { scopesToProbe: [['variable'], ['entity.name.variable']], dark: null, light: null, hc: null }, nls.localize('variables', "Token style for variables.")); +/* +Tags: local, param, reference, write, async, documentation, overloaded +Struct + +Type +Class : Type +Interface : Type + +Field : Property +Method : Property +Property + +Variable +Parameter +Const + +Function + +Macro +Operator + +Namespace +Label +Event + +(Emum) + +Operator +Keyword + +literal +string : literal +number : literal +regex: literal + +*/ /** * @param colorValue Resolve a color value in the context of a theme */ From b351673fac97975912bb1386eff213ebdc32d9e9 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 27 Sep 2019 16:54:45 +0200 Subject: [PATCH 08/49] TokenTypes & TokenModifiers --- .../browser/standaloneThemeServiceImpl.ts | 8 +- .../test/browser/standaloneLanguages.test.ts | 5 +- src/vs/platform/theme/common/themeService.ts | 6 +- .../common/tokenClassificationRegistry.ts | 345 ++++++++++++++++++ .../theme/common/tokenStyleRegistry.ts | 327 ----------------- .../theme/test/common/testThemeService.ts | 10 +- .../terminalColorRegistry.test.ts | 4 +- .../services/themes/common/colorThemeData.ts | 121 +++--- .../tokenStyleResolving.test.ts | 74 ++-- 9 files changed, 465 insertions(+), 435 deletions(-) create mode 100644 src/vs/platform/theme/common/tokenClassificationRegistry.ts delete mode 100644 src/vs/platform/theme/common/tokenStyleRegistry.ts diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index b26e44bf76c..61cf4274a0d 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -14,7 +14,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { Registry } from 'vs/platform/registry/common/platform'; import { ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/common/colorRegistry'; import { Extensions as ThemingExtensions, ICssStyleCollector, IIconTheme, IThemingRegistry } from 'vs/platform/theme/common/themeService'; -import { TokenStyle, TokenStyleIdentifier } from 'vs/platform/theme/common/tokenStyleRegistry'; +import { TokenStyle, TokenClassification, ProbeScope } from 'vs/platform/theme/common/tokenClassificationRegistry'; const VS_THEME_NAME = 'vs'; const VS_DARK_THEME_NAME = 'vs-dark'; @@ -131,7 +131,11 @@ class StandaloneTheme implements IStandaloneTheme { return this._tokenTheme; } - getTokenStyle(tokenStyle: TokenStyleIdentifier, useDefault?: boolean | undefined): TokenStyle | undefined { + getTokenStyle(classification: TokenClassification, useDefault?: boolean | undefined): TokenStyle | undefined { + return undefined; + } + + resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined { return undefined; } } diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index 5dfa7d92149..4be6f232107 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -13,7 +13,6 @@ import { ILineTokens, IToken, TokenizationSupport2Adapter, TokensProvider } from import { IStandaloneTheme, IStandaloneThemeData, IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { IIconTheme, ITheme, LIGHT } from 'vs/platform/theme/common/themeService'; -import { TokenStyleIdentifier } from 'vs/platform/theme/common/tokenStyleRegistry'; suite('TokenizationSupport2Adapter', () => { @@ -57,7 +56,9 @@ suite('TokenizationSupport2Adapter', () => { throw new Error('Not implemented'); }, - getTokenStyle: (tokenStyleId: TokenStyleIdentifier) => undefined + getTokenStyle: () => undefined, + + }; } public getIconTheme(): IIconTheme { diff --git a/src/vs/platform/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index 7ac9788d1a7..8117fd486a3 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -10,7 +10,7 @@ import * as platform from 'vs/platform/registry/common/platform'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Event, Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { TokenStyleIdentifier, TokenStyle } from 'vs/platform/theme/common/tokenStyleRegistry'; +import { TokenStyle, TokenClassification, ProbeScope } from 'vs/platform/theme/common/tokenClassificationRegistry'; export const IThemeService = createDecorator('themeService'); @@ -61,7 +61,9 @@ export interface ITheme { */ defines(color: ColorIdentifier): boolean; - getTokenStyle(color: TokenStyleIdentifier, useDefault?: boolean): TokenStyle | undefined; + getTokenStyle(classification: TokenClassification, useDefault?: boolean): TokenStyle | undefined; + + resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined; } export interface IIconTheme { diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts new file mode 100644 index 00000000000..d1778608f8f --- /dev/null +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -0,0 +1,345 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as platform from 'vs/platform/registry/common/platform'; +import { Color } from 'vs/base/common/color'; +import { ITheme } from 'vs/platform/theme/common/themeService'; +import * as nls from 'vs/nls'; + +import { editorForeground } from 'vs/platform/theme/common/colorRegistry'; + +// ------ API types + +export const TOKEN_TYPE_WILDCARD = '*'; +export const TOKEN_TYPE_WILDCARD_NUM = -1; + +// qualified string [type|*](.modifier)* +export type TokenClassificationString = string; + +export interface TokenClassification { + type: number; + modifiers: number; +} + +export interface TokenTypeOrModifierContribution { + readonly num: number; + readonly id: string; + readonly description: string; + readonly deprecationMessage: string | undefined; +} + + +export interface TokenStyleData { + foreground?: Color; + bold?: boolean; + underline?: boolean; + italic?: boolean; +} + +export class TokenStyle implements Readonly { + constructor( + public readonly foreground?: Color, + public readonly bold?: boolean, + public readonly underline?: boolean, + public readonly italic?: boolean, + ) { + } +} + +export namespace TokenStyle { + export function fromData(data: { foreground?: Color, bold?: boolean, underline?: boolean, italic?: boolean }) { + return new TokenStyle(data.foreground, data.bold, data.underline, data.italic); + } +} + +export type ProbeScope = string[]; + +export interface TokenStyleFunction { + (theme: ITheme): TokenStyle | undefined; +} + +export interface TokenStyleDefaults { + scopesToProbe: ProbeScope[]; + light: TokenStyleValue | null; + dark: TokenStyleValue | null; + hc: TokenStyleValue | null; +} + +export interface TokenStylingDefaultRule { + classification: TokenClassification; + matchScore: number; + defaults: TokenStyleDefaults; +} + +export interface TokenStylingRule { + classification: TokenClassification; + matchScore: number; + value: TokenStyle; +} + +/** + * A TokenStyle Value is either a token style literal, or a TokenClassificationString + */ +export type TokenStyleValue = TokenStyle | TokenClassificationString; + +// TokenStyle registry +export const Extensions = { + TokenClassificationContribution: 'base.contributions.tokenClassification' +}; + +export interface ITokenClassificationRegistry { + + /** + * Register a token type to the registry. + * @param id The TokenType id as used in theme description files + * @description the description + */ + registerTokenType(id: string, description: string): void; + + /** + * Register a token modifier to the registry. + * @param id The TokenModifier id as used in theme description files + * @description the description + */ + registerTokenModifier(id: string, description: string): void; + + getTokenClassificationFromString(str: TokenClassificationString): TokenClassification | undefined; + getTokenClassification(type: string, modifiers: string[]): TokenClassification | undefined; + + /** + * Register a TokenStyle default to the registry. + * @param selector The rule selector + * @param defaults The default values + */ + registerTokenStyleDefault(selector: TokenClassification, defaults: TokenStyleDefaults): void; + + /** + * Deregister a TokenType from the registry. + */ + deregisterTokenType(id: string): void; + + /** + * Deregister a TokenModifier from the registry. + */ + deregisterTokenModifier(id: string): void; + + /** + * Get all TokenType contributions + */ + getTokenTypes(): TokenTypeOrModifierContribution[]; + + /** + * Get all TokenModifier contributions + */ + getTokenModifiers(): TokenTypeOrModifierContribution[]; + + /** + * Resolves a token classification against the given rules and default rules from the registry. + */ + resolveTokenStyle(classification: TokenClassification, themingRules: TokenStylingRule[], useDefault: boolean, theme: ITheme): TokenStyle | undefined; +} + + + +class TokenClassificationRegistry implements ITokenClassificationRegistry { + + private currentTypeNumber = 0; + private currentModifierBit = 1; + + private tokenTypeById: { [key: string]: TokenTypeOrModifierContribution }; + private tokenModifierById: { [key: string]: TokenTypeOrModifierContribution }; + + private tokenStylingDefaultRules: TokenStylingDefaultRule[] = []; + + constructor() { + this.tokenTypeById = {}; + this.tokenModifierById = {}; + + this.tokenTypeById[TOKEN_TYPE_WILDCARD] = { num: TOKEN_TYPE_WILDCARD_NUM, id: TOKEN_TYPE_WILDCARD, description: '', deprecationMessage: undefined }; + } + + public registerTokenType(id: string, description: string, deprecationMessage?: string): void { + const num = this.currentTypeNumber++; + let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, description, deprecationMessage }; + this.tokenTypeById[id] = tokenStyleContribution; + } + + public registerTokenModifier(id: string, description: string, deprecationMessage?: string): void { + const num = this.currentModifierBit; + this.currentModifierBit = this.currentModifierBit * 2; + let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, description, deprecationMessage }; + this.tokenModifierById[id] = tokenStyleContribution; + } + + public getTokenClassification(type: string, modifiers: string[]): TokenClassification | undefined { + const tokenTypeDesc = this.tokenTypeById[type]; + if (!tokenTypeDesc) { + return undefined; + } + let allModifierBits = 0; + for (const modifier of modifiers) { + const tokenModifierDesc = this.tokenModifierById[modifier]; + if (tokenModifierDesc) { + allModifierBits |= tokenModifierDesc.num; + } + } + return { type: tokenTypeDesc.num, modifiers: allModifierBits }; + } + + public getTokenClassificationFromString(str: TokenClassificationString): TokenClassification | undefined { + const parts = str.split('.'); + const type = parts.shift(); + if (type) { + return this.getTokenClassification(type, parts); + } + return undefined; + } + + public registerTokenStyleDefault(classification: TokenClassification, defaults: TokenStyleDefaults): void { + const matchScore = bitCount(classification.modifiers) + ((classification.type !== TOKEN_TYPE_WILDCARD_NUM) ? 1 : 0); + this.tokenStylingDefaultRules.push({ classification, matchScore, defaults }); + } + + public deregisterTokenType(id: string): void { + delete this.tokenTypeById[id]; + } + + public deregisterTokenModifier(id: string): void { + delete this.tokenModifierById[id]; + } + + public getTokenTypes(): TokenTypeOrModifierContribution[] { + return Object.keys(this.tokenTypeById).map(id => this.tokenTypeById[id]); + } + + public getTokenModifiers(): TokenTypeOrModifierContribution[] { + return Object.keys(this.tokenModifierById).map(id => this.tokenModifierById[id]); + } + + public resolveTokenStyle(classification: TokenClassification, themingRules: TokenStylingRule[], useDefault: boolean, theme: ITheme): TokenStyle | undefined { + let result: any = { + foreground: theme.getColor(editorForeground), + bold: false, + underline: false, + italic: false + }; + let score = { + foreground: -1, + bold: -1, + underline: -1, + italic: -1 + }; + + function _processStyle(matchScore: number, style: TokenStyle) { + for (let p in result) { + const property = p as keyof TokenStyle; + const info = style[property]; + if (info !== undefined && score[property] <= matchScore) { + score[property] = matchScore; + result[property] = info; + } + } + } + themingRules.forEach(rule => { + const matchScore = match(rule, classification); + if (matchScore >= 0) { + _processStyle(matchScore, rule.value); + } + }); + if (useDefault) { + this.tokenStylingDefaultRules.forEach(rule => { + const matchScore = match(rule, classification); + if (matchScore >= 0) { + let style = theme.resolveScopes(rule.defaults.scopesToProbe); + if (!style) { + style = this.resolveTokenStyleValue(rule.defaults[theme.type], theme); + } + if (style) { + _processStyle(matchScore, style); + } + } + }); + } + return TokenStyle.fromData(result); + } + + /** + * @param tokenStyleValue Resolve a tokenStyleValue in the context of a theme + */ + private resolveTokenStyleValue(tokenStyleValue: TokenStyleValue | null, theme: ITheme): TokenStyle | undefined { + if (tokenStyleValue === null) { + return undefined; + } else if (typeof tokenStyleValue === 'string') { + const classification = this.getTokenClassificationFromString(tokenStyleValue); + if (classification) { + return theme.getTokenStyle(classification); + } + } else if (typeof tokenStyleValue === 'object') { + return tokenStyleValue; + } + return undefined; + } + + + public toString() { + let sorter = (a: string, b: string) => { + let cat1 = a.indexOf('.') === -1 ? 0 : 1; + let cat2 = b.indexOf('.') === -1 ? 0 : 1; + if (cat1 !== cat2) { + return cat1 - cat2; + } + return a.localeCompare(b); + }; + + return Object.keys(this.tokenTypeById).sort(sorter).map(k => `- \`${k}\`: ${this.tokenTypeById[k].description}`).join('\n'); + } + +} + +function match(themeSelector: TokenStylingRule | TokenStylingDefaultRule, classification: TokenClassification): number { + const selectorType = themeSelector.classification.type; + if (selectorType !== TOKEN_TYPE_WILDCARD_NUM && selectorType === classification.type) { + return -1; + } + const selectorModifier = themeSelector.classification.modifiers; + if ((classification.modifiers & selectorModifier) !== selectorModifier) { + return -1; + } + return themeSelector.matchScore; +} + + +const tokenClassificationRegistry = new TokenClassificationRegistry(); +platform.Registry.add(Extensions.TokenClassificationContribution, tokenClassificationRegistry); + +export function registerTokenType(id: string, defaults: TokenStyleDefaults | null, description: string, deprecationMessage?: string): string { + tokenClassificationRegistry.registerTokenType(id, description, deprecationMessage); + + if (defaults) { + const classification = tokenClassificationRegistry.getTokenClassification(id, []); + tokenClassificationRegistry.registerTokenStyleDefault(classification!, defaults); + } + return id; +} + +export function getTokenClassificationRegistry(): ITokenClassificationRegistry { + return tokenClassificationRegistry; +} + +export const comments = registerTokenType('comments', { scopesToProbe: [['comment']], dark: null, light: null, hc: null }, nls.localize('comments', "Token style for comments.")); +export const strings = registerTokenType('strings', { scopesToProbe: [['string']], dark: null, light: null, hc: null }, nls.localize('strings', "Token style for strings.")); +export const keywords = registerTokenType('keywords', { scopesToProbe: [['keyword.control'], ['storage'], ['storage.type']], dark: null, light: null, hc: null }, nls.localize('keywords', "Token style for keywords.")); +export const numbers = registerTokenType('numbers', { scopesToProbe: [['constant.numeric']], dark: null, light: null, hc: null }, nls.localize('numbers', "Token style for numbers.")); +export const types = registerTokenType('types', { scopesToProbe: [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']], dark: null, light: null, hc: null }, nls.localize('types', "Token style for types.")); +export const functions = registerTokenType('functions', { scopesToProbe: [['entity.name.function'], ['support.function']], dark: null, light: null, hc: null }, nls.localize('functions', "Token style for functions.")); +export const variables = registerTokenType('variables', { scopesToProbe: [['variable'], ['entity.name.variable']], dark: null, light: null, hc: null }, nls.localize('variables', "Token style for variables.")); + + + +function bitCount(u: number) { + // https://blogs.msdn.microsoft.com/jeuge/2005/06/08/bit-fiddling-3/ + const uCount = u - ((u >> 1) & 0o33333333333) - ((u >> 2) & 0o11111111111); + return ((uCount + (uCount >> 3)) & 0o30707070707) % 63; +} diff --git a/src/vs/platform/theme/common/tokenStyleRegistry.ts b/src/vs/platform/theme/common/tokenStyleRegistry.ts deleted file mode 100644 index 45f548b3520..00000000000 --- a/src/vs/platform/theme/common/tokenStyleRegistry.ts +++ /dev/null @@ -1,327 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as platform from 'vs/platform/registry/common/platform'; -import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; -import { Color } from 'vs/base/common/color'; -import { ITheme } from 'vs/platform/theme/common/themeService'; -import { Event, Emitter } from 'vs/base/common/event'; -import * as nls from 'vs/nls'; - -import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; -import { RunOnceScheduler } from 'vs/base/common/async'; -import { editorForeground } from 'vs/platform/theme/common/colorRegistry'; - -// ------ API types - -export type TokenStyleIdentifier = string; - -export interface TokenStyleContribution { - readonly id: TokenStyleIdentifier; - readonly description: string; - readonly defaults: TokenStyleDefaults | null; - readonly deprecationMessage: string | undefined; -} - -export const enum TokenStyleBits { - BOLD = 0x01, - UNDERLINE = 0x02, - ITALIC = 0x04 -} - -export class TokenStyle { - constructor( - public readonly foreground?: Color, - public readonly background?: Color, - public readonly styles?: number - ) { - - } - - hasStyle(style: number): boolean { - return !!this.styles && ((this.styles & style) === style); - } -} - -export namespace TokenStyle { - export function fromString(s: string) { - const parts = s.split('-'); - let part = parts.shift(); - if (part) { - const foreground = Color.fromHex(part); - let background = undefined; - let style = undefined; - part = parts.shift(); - if (part && part[0] === '#') { - background = Color.fromHex(part); - part = parts.shift(); - } - if (part) { - try { - style = parseInt(part); - } catch (e) { - // ignore - } - } - return new TokenStyle(foreground, background, style); - } - return new TokenStyle(Color.red); - } -} - -export type ProbeScope = string[]; - -export interface TokenStyleFunction { - (theme: ITheme): TokenStyle | undefined; -} - -export interface TokenStyleDefaults { - scopesToProbe?: ProbeScope[]; - light: TokenStyleValue | null; - dark: TokenStyleValue | null; - hc: TokenStyleValue | null; -} - -/** - * A TokenStyle Value is either a token style literal, a reference to other token style or a derived token style - */ -export type TokenStyleValue = TokenStyle | string | TokenStyleIdentifier | TokenStyleFunction; - -// TokenStyle registry -export const Extensions = { - TokenStyleContribution: 'base.contributions.tokenStyles' -}; - -export interface ITokenStyleRegistry { - - readonly onDidChangeSchema: Event; - - /** - * Register a TokenStyle to the registry. - * @param id The TokenStyle id as used in theme description files - * @param defaults The default values - * @description the description - */ - registerTokenStyle(id: string, defaults: TokenStyleDefaults, description: string): TokenStyleIdentifier; - - /** - * Register a TokenStyle to the registry. - */ - deregisterTokenStyle(id: string): void; - - /** - * Get all TokenStyle contributions - */ - getTokenStyles(): TokenStyleContribution[]; - - /** - * Gets the default TokenStyle of the given id - */ - resolveDefaultTokenStyle(id: TokenStyleIdentifier, theme: ITheme, findTokenStyleForScope: (scope: ProbeScope) => TokenStyle | undefined): TokenStyle | undefined; - - /** - * JSON schema for an object to assign TokenStyle values to one of the TokenStyle contributions. - */ - getTokenStyleSchema(): IJSONSchema; - - /** - * JSON schema to for a reference to a TokenStyle contribution. - */ - getTokenStyleReferenceSchema(): IJSONSchema; - -} - - - -class TokenStyleRegistry implements ITokenStyleRegistry { - - private readonly _onDidChangeSchema = new Emitter(); - readonly onDidChangeSchema: Event = this._onDidChangeSchema.event; - - private tokenStyleById: { [key: string]: TokenStyleContribution }; - private tokenStyleSchema: IJSONSchema & { properties: IJSONSchemaMap } = { type: 'object', properties: {} }; - private tokenStyleReferenceSchema: IJSONSchema & { enum: string[], enumDescriptions: string[] } = { type: 'string', enum: [], enumDescriptions: [] }; - - constructor() { - this.tokenStyleById = {}; - } - - public registerTokenStyle(id: string, defaults: TokenStyleDefaults | null, description: string, deprecationMessage?: string): TokenStyleIdentifier { - let tokenStyleContribution: TokenStyleContribution = { id, description, defaults, deprecationMessage }; - this.tokenStyleById[id] = tokenStyleContribution; - let propertySchema: IJSONSchema = { - type: 'object', - description, - properties: { - 'foreground': { type: 'string', format: 'color-hex', default: '#ff0000' }, - 'italic': { type: 'boolean' }, - 'bold': { type: 'boolean' }, - 'underline': { type: 'boolean' } - } - }; - if (deprecationMessage) { - propertySchema.deprecationMessage = deprecationMessage; - } - this.tokenStyleSchema.properties[id] = propertySchema; - this.tokenStyleReferenceSchema.enum.push(id); - this.tokenStyleReferenceSchema.enumDescriptions.push(description); - - this._onDidChangeSchema.fire(); - return id; - } - - - public deregisterTokenStyle(id: string): void { - delete this.tokenStyleById[id]; - delete this.tokenStyleSchema.properties[id]; - const index = this.tokenStyleReferenceSchema.enum.indexOf(id); - if (index !== -1) { - this.tokenStyleReferenceSchema.enum.splice(index, 1); - this.tokenStyleReferenceSchema.enumDescriptions.splice(index, 1); - } - this._onDidChangeSchema.fire(); - } - - public getTokenStyles(): TokenStyleContribution[] { - return Object.keys(this.tokenStyleById).map(id => this.tokenStyleById[id]); - } - - public resolveDefaultTokenStyle(id: TokenStyleIdentifier, theme: ITheme, findTokenStyleForScope: (scope: ProbeScope) => TokenStyle | undefined): TokenStyle | undefined { - const tokenStyleDesc = this.tokenStyleById[id]; - if (tokenStyleDesc && tokenStyleDesc.defaults) { - const scopesToProbe = tokenStyleDesc.defaults.scopesToProbe; - if (scopesToProbe) { - for (let scope of scopesToProbe) { - const style = findTokenStyleForScope(scope); - if (style) { - return style; - } - } - } - const tokenStyleValue = tokenStyleDesc.defaults[theme.type]; - if (tokenStyleValue === null) { - return new TokenStyle(theme.getColor(editorForeground)); - } - return resolveTokenStyleValue(tokenStyleValue, theme); - } - return undefined; - } - - public getTokenStyleSchema(): IJSONSchema { - return this.tokenStyleSchema; - } - - public getTokenStyleReferenceSchema(): IJSONSchema { - return this.tokenStyleReferenceSchema; - } - - public toString() { - let sorter = (a: string, b: string) => { - let cat1 = a.indexOf('.') === -1 ? 0 : 1; - let cat2 = b.indexOf('.') === -1 ? 0 : 1; - if (cat1 !== cat2) { - return cat1 - cat2; - } - return a.localeCompare(b); - }; - - return Object.keys(this.tokenStyleById).sort(sorter).map(k => `- \`${k}\`: ${this.tokenStyleById[k].description}`).join('\n'); - } - -} - -const tokenStyleRegistry = new TokenStyleRegistry(); -platform.Registry.add(Extensions.TokenStyleContribution, tokenStyleRegistry); - -export function registerTokenStyle(id: string, defaults: TokenStyleDefaults | null, description: string, deprecationMessage?: string): TokenStyleIdentifier { - return tokenStyleRegistry.registerTokenStyle(id, defaults, description, deprecationMessage); -} - -export function getTokenStyleRegistry(): ITokenStyleRegistry { - return tokenStyleRegistry; -} - -// colors - - -export const comments = registerTokenStyle('comments', { scopesToProbe: [['comment']], dark: null, light: null, hc: null }, nls.localize('comments', "Token style for comments.")); -export const strings = registerTokenStyle('strings', { scopesToProbe: [['string']], dark: null, light: null, hc: null }, nls.localize('strings', "Token style for strings.")); -export const keywords = registerTokenStyle('keywords', { scopesToProbe: [['keyword.control'], ['storage'], ['storage.type']], dark: null, light: null, hc: null }, nls.localize('keywords', "Token style for keywords.")); -export const numbers = registerTokenStyle('numbers', { scopesToProbe: [['constant.numeric']], dark: null, light: null, hc: null }, nls.localize('numbers', "Token style for numbers.")); -export const types = registerTokenStyle('types', { scopesToProbe: [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']], dark: null, light: null, hc: null }, nls.localize('types', "Token style for types.")); -export const functions = registerTokenStyle('functions', { scopesToProbe: [['entity.name.function'], ['support.function']], dark: null, light: null, hc: null }, nls.localize('functions', "Token style for functions.")); -export const variables = registerTokenStyle('variables', { scopesToProbe: [['variable'], ['entity.name.variable']], dark: null, light: null, hc: null }, nls.localize('variables', "Token style for variables.")); -/* -Tags: local, param, reference, write, async, documentation, overloaded - -Struct - -Type -Class : Type -Interface : Type - -Field : Property -Method : Property -Property - -Variable -Parameter -Const - -Function - -Macro -Operator - -Namespace -Label -Event - -(Emum) - -Operator -Keyword - -literal -string : literal -number : literal -regex: literal - -*/ -/** - * @param colorValue Resolve a color value in the context of a theme - */ -function resolveTokenStyleValue(tokenStyleValue: TokenStyleValue | null, theme: ITheme): TokenStyle | undefined { - if (tokenStyleValue === null) { - return undefined; - } else if (typeof tokenStyleValue === 'string') { - if (tokenStyleValue[0] === '#') { - return TokenStyle.fromString(tokenStyleValue); - } - return theme.getTokenStyle(tokenStyleValue); - } else if (typeof tokenStyleValue === 'object') { - return tokenStyleValue; - } else if (typeof tokenStyleValue === 'function') { - return tokenStyleValue(theme); - } - return undefined; -} - -export const tokenStyleColorsSchemaId = 'vscode://schemas/workbench-tokenstyles'; - -let schemaRegistry = platform.Registry.as(JSONExtensions.JSONContribution); -schemaRegistry.registerSchema(tokenStyleColorsSchemaId, tokenStyleRegistry.getTokenStyleSchema()); - -const delayer = new RunOnceScheduler(() => schemaRegistry.notifySchemaChanged(tokenStyleColorsSchemaId), 200); -tokenStyleRegistry.onDidChangeSchema(() => { - if (!delayer.isScheduled()) { - delayer.schedule(); - } -}); - -// setTimeout(_ => console.log(colorRegistry.toString()), 5000); - - - diff --git a/src/vs/platform/theme/test/common/testThemeService.ts b/src/vs/platform/theme/test/common/testThemeService.ts index e384b457389..b9279dc39ee 100644 --- a/src/vs/platform/theme/test/common/testThemeService.ts +++ b/src/vs/platform/theme/test/common/testThemeService.ts @@ -6,7 +6,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IThemeService, ITheme, DARK, IIconTheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; -import { TokenStyle, TokenStyleIdentifier } from 'vs/platform/theme/common/tokenStyleRegistry'; +import { TokenStyle, TokenClassification, ProbeScope } from 'vs/platform/theme/common/tokenClassificationRegistry'; export class TestTheme implements ITheme { @@ -25,8 +25,12 @@ export class TestTheme implements ITheme { throw new Error('Method not implemented.'); } - getTokenStyle(tokenStyleIdentifier: TokenStyleIdentifier, useDefault?: boolean | undefined): TokenStyle | undefined { - return undefined; + getTokenStyle(classification: TokenClassification, useDefault?: boolean | undefined): TokenStyle | undefined { + throw new Error('Method not implemented.'); + } + + resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined { + throw new Error('Method not implemented.'); } } diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts index d2d64fa1a24..1026d3a91a1 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts @@ -9,7 +9,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ansiColorIdentifiers, registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { ITheme, ThemeType } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; -import { TokenStyleIdentifier } from 'vs/platform/theme/common/tokenStyleRegistry'; +import { TokenClassification } from 'vs/platform/theme/common/tokenClassificationRegistry'; registerColors(); @@ -21,7 +21,7 @@ function getMockTheme(type: ThemeType): ITheme { type: type, getColor: (colorId: ColorIdentifier): Color | undefined => themingRegistry.resolveDefaultColor(colorId, theme), defines: () => true, - getTokenStyle: (tokenStyleId: TokenStyleIdentifier) => undefined + getTokenStyle: (tokenStyleId: TokenClassification) => undefined }; return theme; } diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 4dbdfcc2eff..53fa06ef149 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -20,12 +20,12 @@ import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { parse as parsePList } from 'vs/workbench/services/themes/common/plistParser'; import { startsWith } from 'vs/base/common/strings'; -import { Extensions as TokenStyleRegistryExtensions, TokenStyle, TokenStyleIdentifier, ITokenStyleRegistry, TokenStyleBits, ProbeScope } from 'vs/platform/theme/common/tokenStyleRegistry'; +import { Extensions as TokenStyleRegistryExtensions, TokenStyle, TokenClassification, ITokenClassificationRegistry, ProbeScope, TokenStylingRule } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { MatcherWithPriority, Matcher, createMatchers } from 'vs/workbench/services/themes/common/textMateScopeMatcher'; let colorRegistry = Registry.as(ColorRegistryExtensions.ColorContribution); -let tokenStyleRegistry = Registry.as(TokenStyleRegistryExtensions.TokenStyleContribution); +let tokenClassificationRegistry = Registry.as(TokenStyleRegistryExtensions.TokenClassificationContribution); const tokenGroupToScopesMap = { comments: ['comment'], @@ -37,9 +37,6 @@ const tokenGroupToScopesMap = { variables: ['variable', 'entity.name.variable'] }; -interface ITokenStyleMap { - [id: string]: TokenStyle; -} export class ColorThemeData implements IColorTheme { @@ -57,7 +54,7 @@ export class ColorThemeData implements IColorTheme { private colorMap: IColorMap = {}; private customColorMap: IColorMap = {}; - private tokenStyleMap: ITokenStyleMap = {}; + private tokenStylingRules: TokenStylingRule[] = []; private themeTokenScopeMatchers: Matcher[] | undefined; private customTokenScopeMatchers: Matcher[] | undefined; @@ -116,73 +113,57 @@ export class ColorThemeData implements IColorTheme { return color; } - public getTokenStyle(tokenStyleId: TokenStyleIdentifier, useDefault?: boolean): TokenStyle | undefined { - let style: TokenStyle | undefined = this.tokenStyleMap[tokenStyleId]; - if (style) { - return style; - } - if (useDefault !== false && types.isUndefined(style)) { - style = this.getDefaultTokenStyle(tokenStyleId); - } - return style; + public getTokenStyle(tokenClassification: TokenClassification, useDefault?: boolean): TokenStyle | undefined { + // todo: cache results + return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, this.tokenStylingRules, !!useDefault, this); } public getDefault(colorId: ColorIdentifier): Color | undefined { return colorRegistry.resolveDefaultColor(colorId, this); } - public getDefaultTokenStyle(tokenStyleId: TokenStyleIdentifier): TokenStyle | undefined { - return tokenStyleRegistry.resolveDefaultTokenStyle(tokenStyleId, this, this.findTokenStyleForScope.bind(this)); + public getDefaultTokenStyle(tokenClassification: TokenClassification): TokenStyle | undefined { + return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, [], true, this); } - /** Public for testing reasons */ - public findTokenStyleForScope(scope: ProbeScope): TokenStyle | undefined { - - let foregroundColor = this.getColor(editorForeground); - - let foreground = foregroundColor ? foregroundColor.toString() : null; - let fontStyle: string | null = null; - let foregroundScore = -1; - let fontStyleScore = -1; - - function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITokenColorizationRule[]) { - for (let i = 0; i < scopeMatchers.length; i++) { - const settings = tokenColors[i].settings; - if (!settings) { - continue; - } - let matcher = scopeMatchers[i]; - if (!matcher) { - scopeMatchers[i] = matcher = getScopeMatcher(tokenColors[i]); - } - const score = matcher(scope); - if (score >= 0) { - if (score >= foregroundScore && settings.foreground) { - foreground = settings.foreground; - } - if (score >= fontStyleScore && types.isString(settings.fontStyle)) { - fontStyle = settings.fontStyle; - } - } - - } - } + public resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined { if (!this.themeTokenScopeMatchers) { - this.themeTokenScopeMatchers = new Array(this.themeTokenColors.length); + this.themeTokenScopeMatchers = this.themeTokenColors.map(getScopeMatcher); } - findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors); - if (!this.customTokenScopeMatchers) { - this.customTokenScopeMatchers = new Array(this.customTokenColors.length); + this.customTokenScopeMatchers = this.customTokenColors.map(getScopeMatcher); } - findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors); - return getTokenStyle(foreground, fontStyle); + for (let scope of scopes) { + let foreground: string | null = null; + let fontStyle: string | null = null; + let foregroundScore = -1; + let fontStyleScore = -1; + + function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITokenColorizationRule[]) { + for (let i = 0; i < scopeMatchers.length; i++) { + const score = scopeMatchers[i](scope); + if (score >= 0) { + const settings = tokenColors[i].settings; + if (score >= foregroundScore && settings.foreground) { + foreground = settings.foreground; + } + if (score >= fontStyleScore && types.isString(settings.fontStyle)) { + fontStyle = settings.fontStyle; + } + } + } + } + findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors); + findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors); + if (foreground !== null || fontStyle !== null) { + return getTokenStyle(foreground, fontStyle); + } + } + return undefined; } - - public defines(colorId: ColorIdentifier): boolean { return this.customColorMap.hasOwnProperty(colorId) || this.colorMap.hasOwnProperty(colorId); } @@ -508,12 +489,8 @@ function getScopeMatcher(rule: ITokenColorizationRule): Matcher { return noMatch; } const matchers: MatcherWithPriority[] = []; - if (Array.isArray(ruleScope)) { - for (let rs of ruleScope) { - matchers.push(...createMatchers(rs, nameMatcher)); - } - } else { - matchers.push(...createMatchers(ruleScope, nameMatcher)); + for (let rs of ruleScope) { + matchers.push(...createMatchers(rs, nameMatcher)); } if (matchers.length === 0) { return noMatch; @@ -528,24 +505,16 @@ function getScopeMatcher(rule: ITokenColorizationRule): Matcher { } function getTokenStyle(foreground: string | null, fontStyle: string | null): TokenStyle | undefined { - let styles: number | undefined; let foregroundColor = undefined; if (foreground !== null) { foregroundColor = Color.fromHex(foreground); } - + let bold, underline, italic; if (fontStyle !== null) { - styles = 0; - if (fontStyle.indexOf('underline') !== -1) { - styles |= TokenStyleBits.UNDERLINE; - } - if (fontStyle.indexOf('italic') !== -1) { - styles |= TokenStyleBits.ITALIC; - } - if (fontStyle.indexOf('bold') !== -1) { - styles |= TokenStyleBits.BOLD; - } + bold = fontStyle.indexOf('bold') !== -1; + underline = fontStyle.indexOf('underline') !== -1; + italic = fontStyle.indexOf('italic') !== -1; } - return new TokenStyle(foregroundColor, undefined, styles); + return new TokenStyle(foregroundColor, bold, underline, italic); } 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 12b9e9102da..324a9664a3c 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, TokenStyleBits, comments, variables, types, functions, keywords, numbers, strings } from 'vs/platform/theme/common/tokenStyleRegistry'; +import { Extensions as TokenStyleRegistryExtensions, ITokenClassificationRegistry, TokenStyle, comments, variables, types, functions, keywords, numbers, strings } 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'; @@ -15,30 +15,59 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { getPathFromAmdModule } from 'vs/base/common/amd'; -import { editorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; + +let tokenClassificationRegistry = Registry.as(TokenStyleRegistryExtensions.TokenClassificationContribution); + +const enum TokenStyleBits { + BOLD = 0x01, + UNDERLINE = 0x02, + ITALIC = 0x04 +} + function ts(foreground: string | undefined, styleFlags: number | undefined): TokenStyle { const foregroundColor = isString(foreground) ? Color.fromHex(foreground) : undefined; - return new TokenStyle(foregroundColor, undefined, styleFlags); + let bold, underline, italic; + if (styleFlags !== undefined) { + bold = (styleFlags & TokenStyleBits.BOLD) !== 0; + underline = (styleFlags & TokenStyleBits.UNDERLINE) !== 0; + italic = (styleFlags & TokenStyleBits.ITALIC) !== 0; + } + return new TokenStyle(foregroundColor, bold, underline, italic); } function tokenStyleAsString(ts: TokenStyle | undefined | null) { - return ts ? `${ts.foreground ? ts.foreground.toString() : 'no-foreground'}-${ts.styles ? ts.styles : 'no-styles'}` : 'tokenstyle-undefined'; + if (!ts) { + return 'tokenstyle-undefined'; + } + let str = ts.foreground ? ts.foreground.toString() : 'no-foreground'; + if (ts.bold !== undefined) { + str = ts.bold ? '+B' : '-B'; + } + if (ts.underline !== undefined) { + str = ts.underline ? '+U' : '-U'; + } + if (ts.italic !== undefined) { + str = ts.italic ? '+I' : '-I'; + } + return str; } function assertTokenStyle(actual: TokenStyle | undefined | null, expected: TokenStyle | undefined | null, message?: string) { assert.equal(tokenStyleAsString(actual), tokenStyleAsString(expected), message); } -function assertTokenStyles(themeData: ColorThemeData, expected: { [tokenStyleId: string]: TokenStyle }) { - for (let tokenStyleId in expected) { - const tokenStyle = themeData.getTokenStyle(tokenStyleId); - assertTokenStyle(tokenStyle, expected[tokenStyleId], tokenStyleId); +function assertTokenStyles(themeData: ColorThemeData, expected: { [qualifiedClassifier: string]: TokenStyle }) { + for (let qualifiedClassifier in expected) { + const classification = tokenClassificationRegistry.getTokenClassificationFromString(qualifiedClassifier); + assert.ok(classification, 'Classification not found'); + + const tokenStyle = themeData.getTokenStyle(classification!); + assertTokenStyle(tokenStyle, expected[qualifiedClassifier], qualifiedClassifier); } } - - suite('Themes - TokenStyleResolving', () => { const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); @@ -165,7 +194,7 @@ suite('Themes - TokenStyleResolving', () => { }); - test('resolve resource', async () => { + test('resolveScopes', async () => { const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); const customTokenColors: ITokenColorCustomizations = { @@ -210,34 +239,37 @@ suite('Themes - TokenStyleResolving', () => { themeData.setCustomTokenColors(customTokenColors); let tokenStyle; - let defaultTokenStyle = new TokenStyle(themeData.getColor(editorForeground)); + let defaultTokenStyle = undefined; - tokenStyle = themeData.findTokenStyleForScope(['variable']); + tokenStyle = themeData.resolveScopes([['variable']]); assertTokenStyle(tokenStyle, ts('#F8F8F2', 0), 'variable'); - tokenStyle = themeData.findTokenStyleForScope(['keyword.operator']); + tokenStyle = themeData.resolveScopes([['keyword.operator']]); assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC | TokenStyleBits.BOLD | TokenStyleBits.UNDERLINE), 'keyword'); - tokenStyle = themeData.findTokenStyleForScope(['keyword']); + tokenStyle = themeData.resolveScopes([['keyword']]); assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword'); - tokenStyle = themeData.findTokenStyleForScope(['keyword.operator']); + tokenStyle = themeData.resolveScopes([['keyword.operator']]); assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC | TokenStyleBits.BOLD | TokenStyleBits.UNDERLINE), 'keyword.operator'); - tokenStyle = themeData.findTokenStyleForScope(['keyword.operators']); + tokenStyle = themeData.resolveScopes([['keyword.operators']]); assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword.operators'); - tokenStyle = themeData.findTokenStyleForScope(['storage']); + tokenStyle = themeData.resolveScopes([['storage']]); assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC), 'storage'); - tokenStyle = themeData.findTokenStyleForScope(['storage.type']); + tokenStyle = themeData.resolveScopes([['storage.type']]); assertTokenStyle(tokenStyle, ts('#66D9EF', TokenStyleBits.ITALIC), 'storage.type'); - tokenStyle = themeData.findTokenStyleForScope(['entity.name.class']); + tokenStyle = themeData.resolveScopes([['entity.name.class']]); assertTokenStyle(tokenStyle, ts('#A6E22E', TokenStyleBits.UNDERLINE), 'entity.name.class'); - tokenStyle = themeData.findTokenStyleForScope(['meta.structure.dictionary.json', 'string.quoted.double.json']); + tokenStyle = themeData.resolveScopes([['meta.structure.dictionary.json', 'string.quoted.double.json']]); assertTokenStyle(tokenStyle, ts('#66D9EF', undefined), 'json property'); + tokenStyle = themeData.resolveScopes([['keyword'], ['storage.type'], ['entity.name.class']]); + assertTokenStyle(tokenStyle, ts('#66D9EF', TokenStyleBits.ITALIC), 'storage.type'); + }); }); From 3ec1475b14e277d5750ae145dc9ad0720c91e649 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 27 Sep 2019 17:22:30 +0200 Subject: [PATCH 09/49] fix missing resolveScopes --- .../standalone/test/browser/standaloneLanguages.test.ts | 2 +- .../test/electron-browser/terminalColorRegistry.test.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts index 4be6f232107..3cb4e474b1d 100644 --- a/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts +++ b/src/vs/editor/standalone/test/browser/standaloneLanguages.test.ts @@ -57,7 +57,7 @@ suite('TokenizationSupport2Adapter', () => { }, getTokenStyle: () => undefined, - + resolveScopes: () => undefined }; } diff --git a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts index 1026d3a91a1..10cad170f83 100644 --- a/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts @@ -9,7 +9,6 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ansiColorIdentifiers, registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { ITheme, ThemeType } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; -import { TokenClassification } from 'vs/platform/theme/common/tokenClassificationRegistry'; registerColors(); @@ -21,7 +20,9 @@ function getMockTheme(type: ThemeType): ITheme { type: type, getColor: (colorId: ColorIdentifier): Color | undefined => themingRegistry.resolveDefaultColor(colorId, theme), defines: () => true, - getTokenStyle: (tokenStyleId: TokenClassification) => undefined + getTokenStyle: () => undefined, + resolveScopes: () => undefined + }; return theme; } From 74c44d63a2595c31a6e95f88bf88cdbf0f98a622 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 7 Oct 2019 16:57:52 +0200 Subject: [PATCH 10/49] all types & modifiers --- .../common/tokenClassificationRegistry.ts | 50 +++++++++++++++---- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index d1778608f8f..9b46ae5cbe1 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -314,29 +314,59 @@ function match(themeSelector: TokenStylingRule | TokenStylingDefaultRule, classi const tokenClassificationRegistry = new TokenClassificationRegistry(); platform.Registry.add(Extensions.TokenClassificationContribution, tokenClassificationRegistry); -export function registerTokenType(id: string, defaults: TokenStyleDefaults | null, description: string, deprecationMessage?: string): string { +export function registerTokenType(id: string, description: string, scopesToProbe: ProbeScope[] = [], extendsTC: string | null = null, deprecationMessage?: string): string { tokenClassificationRegistry.registerTokenType(id, description, deprecationMessage); - if (defaults) { + if (scopesToProbe || extendsTC) { const classification = tokenClassificationRegistry.getTokenClassification(id, []); - tokenClassificationRegistry.registerTokenStyleDefault(classification!, defaults); + tokenClassificationRegistry.registerTokenStyleDefault(classification!, { scopesToProbe, light: extendsTC, dark: extendsTC, hc: extendsTC }); } return id; } +export function registerTokenModifier(id: string, description: string, deprecationMessage?: string): string { + tokenClassificationRegistry.registerTokenModifier(id, description, deprecationMessage); + return id; +} + export function getTokenClassificationRegistry(): ITokenClassificationRegistry { return tokenClassificationRegistry; } -export const comments = registerTokenType('comments', { scopesToProbe: [['comment']], dark: null, light: null, hc: null }, nls.localize('comments', "Token style for comments.")); -export const strings = registerTokenType('strings', { scopesToProbe: [['string']], dark: null, light: null, hc: null }, nls.localize('strings', "Token style for strings.")); -export const keywords = registerTokenType('keywords', { scopesToProbe: [['keyword.control'], ['storage'], ['storage.type']], dark: null, light: null, hc: null }, nls.localize('keywords', "Token style for keywords.")); -export const numbers = registerTokenType('numbers', { scopesToProbe: [['constant.numeric']], dark: null, light: null, hc: null }, nls.localize('numbers', "Token style for numbers.")); -export const types = registerTokenType('types', { scopesToProbe: [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']], dark: null, light: null, hc: null }, nls.localize('types', "Token style for types.")); -export const functions = registerTokenType('functions', { scopesToProbe: [['entity.name.function'], ['support.function']], dark: null, light: null, hc: null }, nls.localize('functions', "Token style for functions.")); -export const variables = registerTokenType('variables', { scopesToProbe: [['variable'], ['entity.name.variable']], dark: null, light: null, hc: null }, nls.localize('variables', "Token style for variables.")); +export const comments = registerTokenType('comments', nls.localize('comments', "Token style for comments."), [['comment']]); +export const strings = registerTokenType('strings', nls.localize('strings', "Token style for strings."), [['string']]); +export const keywords = registerTokenType('keywords', nls.localize('keywords', "Token style for keywords."), [['keyword.control']]); +export const numbers = registerTokenType('numbers', nls.localize('numbers', "Token style for numbers."), [['constant.numeric']]); +export const regexp = registerTokenType('regexp', nls.localize('regexp', "Token style for regular expressions."), [['constant.regexp']]); +export const operators = registerTokenType('operators', nls.localize('operator', "Token style for operators."), [['keyword.operator']]); +export const namespaces = registerTokenType('namespaces', nls.localize('namespace', "Token style for namespaces."), [['entity.name.namespace']]); +export const types = registerTokenType('types', nls.localize('types', "Token style for types."), [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']]); +export const structs = registerTokenType('structs', nls.localize('struct', "Token style for struct."), [['storage.type.struct']], types); +export const classes = registerTokenType('classes', nls.localize('class', "Token style for classes."), [['ntity.name.class']], types); +export const interfaces = registerTokenType('interfaces', nls.localize('interface', "Token style for interfaces."), undefined, types); +export const enums = registerTokenType('enums', nls.localize('enum', "Token style for enums."), undefined, types); +export const parameterTypes = registerTokenType('parameterTypes', nls.localize('parameterType', "Token style for parameterTypes."), undefined, types); + +export const functions = registerTokenType('functions', nls.localize('functions', "Token style for functions."), [['entity.name.function'], ['support.function']]); +export const macros = registerTokenType('macros', nls.localize('macro', "Token style for macros."), undefined, functions); + +export const variables = registerTokenType('variables', nls.localize('variables', "Token style for variables."), [['variable'], ['entity.name.variable']]); +export const constants = registerTokenType('constants', nls.localize('constants', "Token style for constants."), undefined, variables); +export const parameters = registerTokenType('parameters', nls.localize('parameters', "Token style for parameters."), undefined, variables); +export const property = registerTokenType('properties', nls.localize('properties', "Token style for properties."), undefined, variables); + +export const labels = registerTokenType('labels', nls.localize('labels', "Token style for labels."), undefined); + +export const m_declaration = registerTokenModifier('declaration', nls.localize('declaration', "Token modifier for declarations."), undefined); +export const m_documentation = registerTokenModifier('documentation', nls.localize('documentation', "Token modifier for documentation."), undefined); +export const m_member = registerTokenModifier('member', nls.localize('member', "Token modifier for member."), undefined); +export const m_static = registerTokenModifier('static', nls.localize('static', "Token modifier for statics."), undefined); +export const m_abstract = registerTokenModifier('abstract', nls.localize('abstract', "Token modifier for abstracts."), undefined); +export const m_deprecated = registerTokenModifier('deprecated', nls.localize('deprecated', "Token modifier for deprecated."), undefined); +export const m_modification = registerTokenModifier('modification', nls.localize('modification', "Token modifier for modification."), undefined); +export const m_async = registerTokenModifier('async', nls.localize('async', "Token modifier for async."), undefined); function bitCount(u: number) { // https://blogs.msdn.microsoft.com/jeuge/2005/06/08/bit-fiddling-3/ From 65bcaa88af7c244c3dd01572ac19c8d82ca7d99d Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 7 Oct 2019 23:05:20 +0200 Subject: [PATCH 11/49] more tests --- .../common/tokenClassificationRegistry.ts | 37 ++++- .../services/themes/common/colorThemeData.ts | 19 ++- .../themes/common/textMateScopeMatcher.ts | 4 +- .../tokenStyleResolving.test.ts | 153 ++++++++++-------- 4 files changed, 130 insertions(+), 83 deletions(-) diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 9b46ae5cbe1..205f8ba33f2 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -108,6 +108,8 @@ export interface ITokenClassificationRegistry { getTokenClassificationFromString(str: TokenClassificationString): TokenClassification | undefined; getTokenClassification(type: string, modifiers: string[]): TokenClassification | undefined; + getTokenStylingRule(classification: TokenClassification | string | undefined, value: TokenStyle): TokenStylingRule | undefined; + /** * Register a TokenStyle default to the registry. * @param selector The rule selector @@ -197,9 +199,18 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { return undefined; } + public getTokenStylingRule(classification: TokenClassification | string | undefined, value: TokenStyle): TokenStylingRule | undefined { + if (typeof classification === 'string') { + classification = this.getTokenClassificationFromString(classification); + } + if (classification) { + return { classification, matchScore: getTokenStylingScore(classification), value }; + } + return undefined; + } + public registerTokenStyleDefault(classification: TokenClassification, defaults: TokenStyleDefaults): void { - const matchScore = bitCount(classification.modifiers) + ((classification.type !== TOKEN_TYPE_WILDCARD_NUM) ? 1 : 0); - this.tokenStylingDefaultRules.push({ classification, matchScore, defaults }); + this.tokenStylingDefaultRules.push({ classification, matchScore: getTokenStylingScore(classification), defaults }); } public deregisterTokenType(id: string): void { @@ -233,12 +244,20 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { }; function _processStyle(matchScore: number, style: TokenStyle) { - for (let p in result) { + if (style.foreground && score.foreground <= matchScore) { + score.foreground = matchScore; + result.foreground = style.foreground; + } + for (let p of ['bold', 'underline', 'italic']) { const property = p as keyof TokenStyle; const info = style[property]; - if (info !== undefined && score[property] <= matchScore) { - score[property] = matchScore; - result[property] = info; + if (info !== undefined) { + if (score[property] < matchScore) { + score[property] = matchScore; + result[property] = info; + } else if (score[property] === matchScore) { + result[property] = result[property] || info; + } } } } @@ -300,7 +319,7 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { function match(themeSelector: TokenStylingRule | TokenStylingDefaultRule, classification: TokenClassification): number { const selectorType = themeSelector.classification.type; - if (selectorType !== TOKEN_TYPE_WILDCARD_NUM && selectorType === classification.type) { + if (selectorType !== TOKEN_TYPE_WILDCARD_NUM && selectorType !== classification.type) { return -1; } const selectorModifier = themeSelector.classification.modifiers; @@ -373,3 +392,7 @@ function bitCount(u: number) { const uCount = u - ((u >> 1) & 0o33333333333) - ((u >> 2) & 0o11111111111); return ((uCount + (uCount >> 3)) & 0o30707070707) % 63; } + +function getTokenStylingScore(classification: TokenClassification) { + return bitCount(classification.modifiers) + ((classification.type !== TOKEN_TYPE_WILDCARD_NUM) ? 1 : 0); +} diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 53fa06ef149..7daee343887 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -20,12 +20,12 @@ import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { parse as parsePList } from 'vs/workbench/services/themes/common/plistParser'; import { startsWith } from 'vs/base/common/strings'; -import { Extensions as TokenStyleRegistryExtensions, TokenStyle, TokenClassification, ITokenClassificationRegistry, ProbeScope, TokenStylingRule } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { TokenStyle, TokenClassification, ProbeScope, TokenStylingRule, getTokenClassificationRegistry } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { MatcherWithPriority, Matcher, createMatchers } from 'vs/workbench/services/themes/common/textMateScopeMatcher'; let colorRegistry = Registry.as(ColorRegistryExtensions.ColorContribution); -let tokenClassificationRegistry = Registry.as(TokenStyleRegistryExtensions.TokenClassificationContribution); +let tokenClassificationRegistry = getTokenClassificationRegistry(); const tokenGroupToScopesMap = { comments: ['comment'], @@ -115,7 +115,7 @@ export class ColorThemeData implements IColorTheme { public getTokenStyle(tokenClassification: TokenClassification, useDefault?: boolean): TokenStyle | undefined { // todo: cache results - return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, this.tokenStylingRules, !!useDefault, this); + return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, this.tokenStylingRules, useDefault !== false, this); } public getDefault(colorId: ColorIdentifier): Color | undefined { @@ -200,6 +200,10 @@ export class ColorThemeData implements IColorTheme { } } + public setTokenStyleRules(tokenStylingRules: TokenStylingRule[]) { + this.tokenStylingRules = tokenStylingRules; + } + private addCustomTokenColors(customTokenColors: ITokenColorCustomizations) { // Put the general customizations such as comments, strings, etc. first so that // they can be overridden by specific customizations like "string.interpolated" @@ -489,9 +493,14 @@ function getScopeMatcher(rule: ITokenColorizationRule): Matcher { return noMatch; } const matchers: MatcherWithPriority[] = []; - for (let rs of ruleScope) { - matchers.push(...createMatchers(rs, nameMatcher)); + if (Array.isArray(ruleScope)) { + for (let rs of ruleScope) { + createMatchers(rs, nameMatcher, matchers); + } + } else { + createMatchers(ruleScope, nameMatcher, matchers); } + if (matchers.length === 0) { return noMatch; } diff --git a/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts b/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts index 701081bca4c..4d1c66adf6f 100644 --- a/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts +++ b/src/vs/workbench/services/themes/common/textMateScopeMatcher.ts @@ -14,8 +14,7 @@ export interface Matcher { (matcherInput: T): number; } -export function createMatchers(selector: string, matchesName: (names: string[], matcherInput: T) => number): MatcherWithPriority[] { - const results = []>[]; +export function createMatchers(selector: string, matchesName: (names: string[], matcherInput: T) => number, results: MatcherWithPriority[]): void { const tokenizer = newTokenizer(selector); let token = tokenizer.next(); while (token !== null) { @@ -38,7 +37,6 @@ export function createMatchers(selector: string, matchesName: (names: string[ } token = tokenizer.next(); } - return results; function parseOperand(): Matcher | null { if (token === '-') { 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 324a9664a3c..b8956caab0a 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 { Extensions as TokenStyleRegistryExtensions, ITokenClassificationRegistry, TokenStyle, comments, variables, types, functions, keywords, numbers, strings } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { TokenStyle, comments, variables, types, functions, keywords, numbers, strings, getTokenClassificationRegistry, TokenStylingRule } 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'; @@ -15,26 +15,15 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { getPathFromAmdModule } from 'vs/base/common/amd'; -import { Registry } from 'vs/platform/registry/common/platform'; -let tokenClassificationRegistry = Registry.as(TokenStyleRegistryExtensions.TokenClassificationContribution); +let tokenClassificationRegistry = getTokenClassificationRegistry(); -const enum TokenStyleBits { - BOLD = 0x01, - UNDERLINE = 0x02, - ITALIC = 0x04 -} +const unsetStyle = { bold: false, underline: false, italic: false }; -function ts(foreground: string | undefined, styleFlags: number | undefined): TokenStyle { +function ts(foreground: string | undefined, styleFlags: { bold?: boolean; underline?: boolean; italic?: boolean } | undefined): TokenStyle { const foregroundColor = isString(foreground) ? Color.fromHex(foreground) : undefined; - let bold, underline, italic; - if (styleFlags !== undefined) { - bold = (styleFlags & TokenStyleBits.BOLD) !== 0; - underline = (styleFlags & TokenStyleBits.UNDERLINE) !== 0; - italic = (styleFlags & TokenStyleBits.ITALIC) !== 0; - } - return new TokenStyle(foregroundColor, bold, underline, italic); + return new TokenStyle(foregroundColor, styleFlags && styleFlags.bold, styleFlags && styleFlags.underline, styleFlags && styleFlags.italic); } function tokenStyleAsString(ts: TokenStyle | undefined | null) { @@ -43,17 +32,25 @@ function tokenStyleAsString(ts: TokenStyle | undefined | null) { } let str = ts.foreground ? ts.foreground.toString() : 'no-foreground'; if (ts.bold !== undefined) { - str = ts.bold ? '+B' : '-B'; + str += ts.bold ? '+B' : '-B'; } if (ts.underline !== undefined) { - str = ts.underline ? '+U' : '-U'; + str += ts.underline ? '+U' : '-U'; } if (ts.italic !== undefined) { - str = ts.italic ? '+I' : '-I'; + str += ts.italic ? '+I' : '-I'; } return str; } +function getTokenStyleRules(rules: [string, TokenStyle][]): TokenStylingRule[] { + return rules.map(e => { + const rule = tokenClassificationRegistry.getTokenStylingRule(e[0], e[1]); + assert.ok(rule); + return rule!; + }); +} + function assertTokenStyle(actual: TokenStyle | undefined | null, expected: TokenStyle | undefined | null, message?: string) { assert.equal(tokenStyleAsString(actual), tokenStyleAsString(expected), message); } @@ -69,6 +66,8 @@ function assertTokenStyles(themeData: ColorThemeData, expected: { [qualifiedClas } suite('Themes - TokenStyleResolving', () => { + + const fileService = new FileService(new NullLogService()); const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); @@ -83,13 +82,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#75715E', 0), - [variables]: ts('#F8F8F2', 0), - [types]: ts('#A6E22E', TokenStyleBits.UNDERLINE), - [functions]: ts('#A6E22E', 0), - [strings]: ts('#E6DB74', 0), - [numbers]: ts('#AE81FF', 0), - [keywords]: ts('#F92672', 0) + [comments]: ts('#75715E', unsetStyle), + [variables]: ts('#F8F8F2', unsetStyle), + [types]: ts('#A6E22E', { underline: true, bold: false, italic: false }), + [functions]: ts('#A6E22E', unsetStyle), + [strings]: ts('#E6DB74', unsetStyle), + [numbers]: ts('#AE81FF', unsetStyle), + [keywords]: ts('#F92672', unsetStyle) }); }); @@ -103,13 +102,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#6A9955', 0), - [variables]: ts('#9CDCFE', 0), - [types]: ts('#4EC9B0', 0), - [functions]: ts('#DCDCAA', 0), - [strings]: ts('#CE9178', 0), - [numbers]: ts('#B5CEA8', 0), - [keywords]: ts('#C586C0', 0) + [comments]: ts('#6A9955', unsetStyle), + [variables]: ts('#9CDCFE', unsetStyle), + [types]: ts('#4EC9B0', unsetStyle), + [functions]: ts('#DCDCAA', unsetStyle), + [strings]: ts('#CE9178', unsetStyle), + [numbers]: ts('#B5CEA8', unsetStyle), + [keywords]: ts('#C586C0', unsetStyle) }); }); @@ -123,13 +122,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#008000', 0), - [variables]: ts('#000000', 0), - [types]: ts('#000000', 0), - [functions]: ts('#000000', 0), - [strings]: ts('#a31515', 0), - [numbers]: ts('#09885a', 0), - [keywords]: ts('#0000ff', 0) + [comments]: ts('#008000', unsetStyle), + [variables]: ts('#000000', unsetStyle), + [types]: ts('#000000', unsetStyle), + [functions]: ts('#000000', unsetStyle), + [strings]: ts('#a31515', unsetStyle), + [numbers]: ts('#09885a', unsetStyle), + [keywords]: ts('#0000ff', unsetStyle) }); }); @@ -143,13 +142,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#7ca668', 0), - [variables]: ts('#9CDCFE', 0), - [types]: ts('#4EC9B0', 0), - [functions]: ts('#DCDCAA', 0), - [strings]: ts('#ce9178', 0), - [numbers]: ts('#b5cea8', 0), - [keywords]: ts('#C586C0', 0) + [comments]: ts('#7ca668', unsetStyle), + [variables]: ts('#9CDCFE', unsetStyle), + [types]: ts('#4EC9B0', unsetStyle), + [functions]: ts('#DCDCAA', unsetStyle), + [strings]: ts('#ce9178', unsetStyle), + [numbers]: ts('#b5cea8', unsetStyle), + [keywords]: ts('#C586C0', unsetStyle) }); }); @@ -163,13 +162,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#a57a4c', 0), - [variables]: ts('#dc3958', 0), - [types]: ts('#f06431', 0), - [functions]: ts('#8ab1b0', 0), - [strings]: ts('#889b4a', 0), - [numbers]: ts('#f79a32', 0), - [keywords]: ts('#98676a', 0) + [comments]: ts('#a57a4c', unsetStyle), + [variables]: ts('#dc3958', unsetStyle), + [types]: ts('#f06431', unsetStyle), + [functions]: ts('#8ab1b0', unsetStyle), + [strings]: ts('#889b4a', unsetStyle), + [numbers]: ts('#f79a32', unsetStyle), + [keywords]: ts('#98676a', unsetStyle) }); }); @@ -183,13 +182,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#384887', 0), - [variables]: ts('#6688cc', 0), - [types]: ts('#ffeebb', TokenStyleBits.UNDERLINE), - [functions]: ts('#ddbb88', 0), - [strings]: ts('#22aa44', 0), - [numbers]: ts('#f280d0', 0), - [keywords]: ts('#225588', 0) + [comments]: ts('#384887', unsetStyle), + [variables]: ts('#6688cc', unsetStyle), + [types]: ts('#ffeebb', { underline: true, bold: false, italic: false }), + [functions]: ts('#ddbb88', unsetStyle), + [strings]: ts('#22aa44', unsetStyle), + [numbers]: ts('#f280d0', unsetStyle), + [keywords]: ts('#225588', unsetStyle) }); }); @@ -242,34 +241,52 @@ suite('Themes - TokenStyleResolving', () => { let defaultTokenStyle = undefined; tokenStyle = themeData.resolveScopes([['variable']]); - assertTokenStyle(tokenStyle, ts('#F8F8F2', 0), 'variable'); + assertTokenStyle(tokenStyle, ts('#F8F8F2', unsetStyle), 'variable'); tokenStyle = themeData.resolveScopes([['keyword.operator']]); - assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC | TokenStyleBits.BOLD | TokenStyleBits.UNDERLINE), 'keyword'); + assertTokenStyle(tokenStyle, ts('#F92672', { italic: true, bold: true, underline: true }), 'keyword'); tokenStyle = themeData.resolveScopes([['keyword']]); assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword'); tokenStyle = themeData.resolveScopes([['keyword.operator']]); - assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC | TokenStyleBits.BOLD | TokenStyleBits.UNDERLINE), 'keyword.operator'); + assertTokenStyle(tokenStyle, ts('#F92672', { italic: true, bold: true, underline: true }), 'keyword.operator'); tokenStyle = themeData.resolveScopes([['keyword.operators']]); assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword.operators'); tokenStyle = themeData.resolveScopes([['storage']]); - assertTokenStyle(tokenStyle, ts('#F92672', TokenStyleBits.ITALIC), 'storage'); + assertTokenStyle(tokenStyle, ts('#F92672', { italic: true, underline: false, bold: false }), 'storage'); tokenStyle = themeData.resolveScopes([['storage.type']]); - assertTokenStyle(tokenStyle, ts('#66D9EF', TokenStyleBits.ITALIC), 'storage.type'); + assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true, underline: false, bold: false }), 'storage.type'); tokenStyle = themeData.resolveScopes([['entity.name.class']]); - assertTokenStyle(tokenStyle, ts('#A6E22E', TokenStyleBits.UNDERLINE), 'entity.name.class'); + assertTokenStyle(tokenStyle, ts('#A6E22E', { underline: true, italic: false, bold: false }), 'entity.name.class'); tokenStyle = themeData.resolveScopes([['meta.structure.dictionary.json', 'string.quoted.double.json']]); assertTokenStyle(tokenStyle, ts('#66D9EF', undefined), 'json property'); tokenStyle = themeData.resolveScopes([['keyword'], ['storage.type'], ['entity.name.class']]); - assertTokenStyle(tokenStyle, ts('#66D9EF', TokenStyleBits.ITALIC), 'storage.type'); + assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true, underline: false, bold: false }), 'storage.type'); + + }); + + test('rule matching', async () => { + const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); + themeData.setCustomColors({ 'editor.foreground': '#000000' }); + themeData.setTokenStyleRules(getTokenStyleRules([ + ['types', ts('#ff0000', undefined)], + ['classes', ts('#0000ff', undefined)], + ['*.static', ts(undefined, { bold: true })], + ['*.declaration', ts(undefined, { italic: true })] + ])); + + assertTokenStyles(themeData, { + 'types': ts('#ff0000', unsetStyle), + 'types.static': ts('#ff0000', { bold: true, italic: false, underline: false }), + 'types.static.declaration': ts('#ff0000', { bold: true, italic: true, underline: false }) + }); }); }); From 4003b647509dc1921c944697efef2c6ac157ba6e Mon Sep 17 00:00:00 2001 From: gjsjohnmurray Date: Mon, 28 Oct 2019 10:27:38 +0000 Subject: [PATCH 12/49] Resolve https://github.com/microsoft/vscode/issues/83139 --- src/vs/workbench/contrib/search/browser/search.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 697ceeb031a..0ff60407271 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -333,7 +333,7 @@ CommandsRegistry.registerCommand({ const RevealInSideBarForSearchResultsCommand: ICommandAction = { id: Constants.RevealInSideBarForSearchResults, - title: nls.localize('revealInSideBar', "Reveal in Explorer") + title: nls.localize('revealInSideBar', "Reveal in Side Bar") }; MenuRegistry.appendMenuItem(MenuId.SearchContext, { From 5b6f03e8c69e04c8e3cf823eba563f73f1f65107 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 6 Nov 2019 11:56:05 +0100 Subject: [PATCH 13/49] adopt merge --- .../extensionResourceLoaderService.ts | 2 +- .../tokenStyleResolving.test.ts | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService.ts b/src/vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService.ts index 2acf10e0b53..744f2860788 100644 --- a/src/vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService.ts +++ b/src/vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService.ts @@ -8,7 +8,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; -class ExtensionResourceLoaderService implements IExtensionResourceLoaderService { +export class ExtensionResourceLoaderService implements IExtensionResourceLoaderService { _serviceBrand: undefined; 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 b8956caab0a..6c0d9af598c 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 @@ -15,6 +15,7 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { getPathFromAmdModule } from 'vs/base/common/amd'; +import { ExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/electron-browser/extensionResourceLoaderService'; let tokenClassificationRegistry = getTokenClassificationRegistry(); @@ -69,6 +70,8 @@ suite('Themes - TokenStyleResolving', () => { const fileService = new FileService(new NullLogService()); + const extensionResourceLoaderService = new ExtensionResourceLoaderService(fileService); + const diskFileSystemProvider = new DiskFileSystemProvider(new NullLogService()); fileService.registerProvider(Schemas.file, diskFileSystemProvider); @@ -77,12 +80,12 @@ suite('Themes - TokenStyleResolving', () => { const themeData = ColorThemeData.createUnloadedTheme('foo'); const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-monokai/themes/monokai-color-theme.json'); themeData.location = URI.file(themeLocation); - await themeData.ensureLoaded(fileService); + await themeData.ensureLoaded(extensionResourceLoaderService); assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#75715E', unsetStyle), + [comments]: ts('#88846f', unsetStyle), [variables]: ts('#F8F8F2', unsetStyle), [types]: ts('#A6E22E', { underline: true, bold: false, italic: false }), [functions]: ts('#A6E22E', unsetStyle), @@ -97,7 +100,7 @@ suite('Themes - TokenStyleResolving', () => { const themeData = ColorThemeData.createUnloadedTheme('foo'); const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/dark_plus.json'); themeData.location = URI.file(themeLocation); - await themeData.ensureLoaded(fileService); + await themeData.ensureLoaded(extensionResourceLoaderService); assert.equal(themeData.isLoaded, true); @@ -117,7 +120,7 @@ suite('Themes - TokenStyleResolving', () => { const themeData = ColorThemeData.createUnloadedTheme('foo'); const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/light_vs.json'); themeData.location = URI.file(themeLocation); - await themeData.ensureLoaded(fileService); + await themeData.ensureLoaded(extensionResourceLoaderService); assert.equal(themeData.isLoaded, true); @@ -137,7 +140,7 @@ suite('Themes - TokenStyleResolving', () => { const themeData = ColorThemeData.createUnloadedTheme('foo'); const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-defaults/themes/hc_black.json'); themeData.location = URI.file(themeLocation); - await themeData.ensureLoaded(fileService); + await themeData.ensureLoaded(extensionResourceLoaderService); assert.equal(themeData.isLoaded, true); @@ -157,7 +160,7 @@ suite('Themes - TokenStyleResolving', () => { const themeData = ColorThemeData.createUnloadedTheme('foo'); const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json'); themeData.location = URI.file(themeLocation); - await themeData.ensureLoaded(fileService); + await themeData.ensureLoaded(extensionResourceLoaderService); assert.equal(themeData.isLoaded, true); @@ -177,7 +180,7 @@ suite('Themes - TokenStyleResolving', () => { const themeData = ColorThemeData.createUnloadedTheme('foo'); const themeLocation = getPathFromAmdModule(require, '../../../../../../../extensions/theme-abyss/themes/abyss-color-theme.json'); themeData.location = URI.file(themeLocation); - await themeData.ensureLoaded(fileService); + await themeData.ensureLoaded(extensionResourceLoaderService); assert.equal(themeData.isLoaded, true); From a9de2f33c3c8b44c6b4dc74e18c5dcff81e16653 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 7 Nov 2019 17:10:45 +0100 Subject: [PATCH 14/49] keep unset attributes as undefined --- .../common/tokenClassificationRegistry.ts | 30 +++--- .../tokenStyleResolving.test.ts | 93 ++++++++++--------- 2 files changed, 63 insertions(+), 60 deletions(-) diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 205f8ba33f2..7817b7a5778 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -8,8 +8,6 @@ import { Color } from 'vs/base/common/color'; import { ITheme } from 'vs/platform/theme/common/themeService'; import * as nls from 'vs/nls'; -import { editorForeground } from 'vs/platform/theme/common/colorRegistry'; - // ------ API types export const TOKEN_TYPE_WILDCARD = '*'; @@ -231,10 +229,10 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { public resolveTokenStyle(classification: TokenClassification, themingRules: TokenStylingRule[], useDefault: boolean, theme: ITheme): TokenStyle | undefined { let result: any = { - foreground: theme.getColor(editorForeground), - bold: false, - underline: false, - italic: false + foreground: undefined, + bold: undefined, + underline: undefined, + italic: undefined }; let score = { foreground: -1, @@ -252,23 +250,15 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { const property = p as keyof TokenStyle; const info = style[property]; if (info !== undefined) { - if (score[property] < matchScore) { + if (score[property] <= matchScore) { score[property] = matchScore; result[property] = info; - } else if (score[property] === matchScore) { - result[property] = result[property] || info; } } } } - themingRules.forEach(rule => { - const matchScore = match(rule, classification); - if (matchScore >= 0) { - _processStyle(matchScore, rule.value); - } - }); if (useDefault) { - this.tokenStylingDefaultRules.forEach(rule => { + for (const rule of this.tokenStylingDefaultRules) { const matchScore = match(rule, classification); if (matchScore >= 0) { let style = theme.resolveScopes(rule.defaults.scopesToProbe); @@ -279,7 +269,13 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { _processStyle(matchScore, style); } } - }); + } + } + for (const rule of themingRules) { + const matchScore = match(rule, classification); + if (matchScore >= 0) { + _processStyle(matchScore, rule.value); + } } return TokenStyle.fromData(result); } 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 6c0d9af598c..a0fe30b8683 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 @@ -19,9 +19,9 @@ import { ExtensionResourceLoaderService } from 'vs/workbench/services/extensionR let tokenClassificationRegistry = getTokenClassificationRegistry(); +const undefinedStyle = { bold: undefined, underline: undefined, italic: undefined }; const unsetStyle = { bold: false, underline: false, italic: false }; - function ts(foreground: string | undefined, styleFlags: { bold?: boolean; underline?: boolean; italic?: boolean } | undefined): TokenStyle { const foregroundColor = isString(foreground) ? Color.fromHex(foreground) : undefined; return new TokenStyle(foregroundColor, styleFlags && styleFlags.bold, styleFlags && styleFlags.underline, styleFlags && styleFlags.italic); @@ -85,13 +85,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#88846f', unsetStyle), + [comments]: ts('#88846f', undefinedStyle), [variables]: ts('#F8F8F2', unsetStyle), [types]: ts('#A6E22E', { underline: true, bold: false, italic: false }), [functions]: ts('#A6E22E', unsetStyle), - [strings]: ts('#E6DB74', unsetStyle), - [numbers]: ts('#AE81FF', unsetStyle), - [keywords]: ts('#F92672', unsetStyle) + [strings]: ts('#E6DB74', undefinedStyle), + [numbers]: ts('#AE81FF', undefinedStyle), + [keywords]: ts('#F92672', undefinedStyle) }); }); @@ -105,13 +105,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#6A9955', unsetStyle), - [variables]: ts('#9CDCFE', unsetStyle), - [types]: ts('#4EC9B0', unsetStyle), - [functions]: ts('#DCDCAA', unsetStyle), - [strings]: ts('#CE9178', unsetStyle), - [numbers]: ts('#B5CEA8', unsetStyle), - [keywords]: ts('#C586C0', unsetStyle) + [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) }); }); @@ -125,13 +125,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#008000', unsetStyle), - [variables]: ts('#000000', unsetStyle), - [types]: ts('#000000', unsetStyle), - [functions]: ts('#000000', unsetStyle), - [strings]: ts('#a31515', unsetStyle), - [numbers]: ts('#09885a', unsetStyle), - [keywords]: ts('#0000ff', unsetStyle) + [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) }); }); @@ -145,13 +145,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#7ca668', unsetStyle), - [variables]: ts('#9CDCFE', unsetStyle), - [types]: ts('#4EC9B0', unsetStyle), - [functions]: ts('#DCDCAA', unsetStyle), - [strings]: ts('#ce9178', unsetStyle), - [numbers]: ts('#b5cea8', unsetStyle), - [keywords]: ts('#C586C0', unsetStyle) + [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) }); }); @@ -165,13 +165,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#a57a4c', unsetStyle), - [variables]: ts('#dc3958', unsetStyle), - [types]: ts('#f06431', unsetStyle), - [functions]: ts('#8ab1b0', unsetStyle), - [strings]: ts('#889b4a', unsetStyle), - [numbers]: ts('#f79a32', unsetStyle), - [keywords]: ts('#98676a', unsetStyle) + [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) }); }); @@ -185,13 +185,13 @@ suite('Themes - TokenStyleResolving', () => { assert.equal(themeData.isLoaded, true); assertTokenStyles(themeData, { - [comments]: ts('#384887', unsetStyle), - [variables]: ts('#6688cc', unsetStyle), + [comments]: ts('#384887', undefinedStyle), + [variables]: ts(undefined, unsetStyle), [types]: ts('#ffeebb', { underline: true, bold: false, italic: false }), [functions]: ts('#ddbb88', unsetStyle), - [strings]: ts('#22aa44', unsetStyle), - [numbers]: ts('#f280d0', unsetStyle), - [keywords]: ts('#225588', unsetStyle) + [strings]: ts('#22aa44', undefinedStyle), + [numbers]: ts('#f280d0', undefinedStyle), + [keywords]: ts('#225588', undefinedStyle) }); }); @@ -280,15 +280,22 @@ suite('Themes - TokenStyleResolving', () => { themeData.setCustomColors({ 'editor.foreground': '#000000' }); themeData.setTokenStyleRules(getTokenStyleRules([ ['types', ts('#ff0000', undefined)], - ['classes', ts('#0000ff', undefined)], + ['classes', ts('#0000ff', { italic: true })], ['*.static', ts(undefined, { bold: true })], - ['*.declaration', ts(undefined, { italic: true })] + ['*.declaration', ts(undefined, { italic: true })], + ['*.async.static', ts('#00ffff', { bold: false, underline: true })], + ['*.async', ts('#000fff', { italic: false, underline: true })] ])); assertTokenStyles(themeData, { - 'types': ts('#ff0000', unsetStyle), - 'types.static': ts('#ff0000', { bold: true, italic: false, underline: false }), - 'types.static.declaration': ts('#ff0000', { bold: true, italic: true, underline: false }) + 'types': ts('#ff0000', undefinedStyle), + 'types.static': ts('#ff0000', { bold: true, italic: undefined, underline: undefined }), + 'types.static.declaration': ts('#ff0000', { bold: true, italic: true, underline: undefined }), + 'classes': ts('#0000ff', { bold: undefined, italic: true, underline: undefined }), + 'classes.static.declaration': ts('#0000ff', { bold: true, italic: true, underline: undefined }), + 'classes.declaration': ts('#0000ff', { bold: undefined, italic: true, underline: undefined }), + 'classes.declaration.async': ts('#000fff', { bold: undefined, italic: false, underline: true }), + 'classes.declaration.async.static': ts('#00ffff', { bold: false, italic: false, underline: true }), }); }); From a5fe35b780b9ed7356f54c5fe14e447a0cf1833c Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 8 Nov 2019 11:27:51 +0100 Subject: [PATCH 15/49] No longer preselected color in colorCustomizations. Fixes #84152 --- src/vs/platform/theme/common/colorRegistry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 89c695d2dee..4c3093b823d 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -103,7 +103,7 @@ class ColorRegistry implements IColorRegistry { public registerColor(id: string, defaults: ColorDefaults | null, description: string, needsTransparency = false, deprecationMessage?: string): ColorIdentifier { let colorContribution: ColorContribution = { id, description, defaults, needsTransparency, deprecationMessage }; this.colorsById[id] = colorContribution; - let propertySchema: IJSONSchema = { type: 'string', description, format: 'color-hex', defaultSnippets: [{ body: '#ff0000' }] }; + let propertySchema: IJSONSchema = { type: 'string', description, format: 'color-hex', defaultSnippets: [{ body: '${1:#ff0000}' }] }; if (deprecationMessage) { propertySchema.deprecationMessage = deprecationMessage; } From 599864aeb01d4e7892d5a69ad3de8144883a5675 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 11 Nov 2019 10:47:56 +0100 Subject: [PATCH 16/49] peek - fix open to side --- .../contrib/gotoSymbol/peek/referencesController.ts | 12 ++++++------ .../contrib/gotoSymbol/peek/referencesWidget.ts | 4 ---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts index 1b5389383e4..aaaede8378e 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts @@ -14,14 +14,14 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import * as editorCommon from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ReferencesModel, OneReference } from '../referencesModel'; -import { ReferenceWidget, LayoutData, ctxReferenceWidgetSearchTreeFocused } from './referencesWidget'; +import { ReferenceWidget, LayoutData } from './referencesWidget'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { Location } from 'vs/editor/common/modes'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { getOuterEditor, PeekContext } from 'vs/editor/contrib/peekView/peekView'; -import { IListService } from 'vs/platform/list/browser/listService'; +import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -361,12 +361,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ mac: { primary: KeyMod.WinCtrl | KeyCode.Enter }, - when: ContextKeyExpr.and(ctxReferenceSearchVisible, ctxReferenceWidgetSearchTreeFocused), + when: ContextKeyExpr.and(ctxReferenceSearchVisible, WorkbenchListFocusContextKey), handler(accessor: ServicesAccessor) { const listService = accessor.get(IListService); - const focus = listService.lastFocusedList && listService.lastFocusedList.getFocus(); - if (focus instanceof OneReference) { - withController(accessor, controller => controller.openReference(focus, true)); + const focus = listService.lastFocusedList?.getFocus(); + if (Array.isArray(focus) && focus[0] instanceof OneReference) { + withController(accessor, controller => controller.openReference(focus[0], true)); } } }); diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts index 00130718f26..82f86384de4 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesWidget.ts @@ -23,7 +23,6 @@ import { Location } from 'vs/editor/common/modes'; import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; import { AriaProvider, DataSource, Delegate, FileReferencesRenderer, OneReferenceRenderer, TreeElement, StringRepresentationProvider, IdentityProvider } from 'vs/editor/contrib/gotoSymbol/peek/referencesTree'; import * as nls from 'vs/nls'; -import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -182,8 +181,6 @@ export interface SelectionEvent { readonly element?: Location; } -export const ctxReferenceWidgetSearchTreeFocused = new RawContextKey('referenceSearchTreeFocused', true); - /** * ZoneWidget that is shown inside the editor */ @@ -319,7 +316,6 @@ export class ReferenceWidget extends peekView.PeekViewWidget { this._instantiationService.createInstance(DataSource), treeOptions ); - ctxReferenceWidgetSearchTreeFocused.bindTo(this._tree.contextKeyService); // split stuff this._splitView.addView({ From 1a644f628cba4dd63b4f7655f69795fb55b57f55 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 11 Nov 2019 10:52:59 +0100 Subject: [PATCH 17/49] #84283 Use log service --- .../workbench/api/common/extHostConfiguration.ts | 16 +++++++++++----- .../api/extHostConfiguration.test.ts | 14 +++++++++----- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index 44b064059fa..9acade4b04c 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -20,6 +20,7 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { Barrier } from 'vs/base/common/async'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; +import { ILogService } from 'vs/platform/log/common/log'; function lookUp(tree: any, key: string) { if (key) { @@ -45,16 +46,19 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { readonly _serviceBrand: undefined; private readonly _proxy: MainThreadConfigurationShape; + private readonly _logService: ILogService; private readonly _extHostWorkspace: ExtHostWorkspace; private readonly _barrier: Barrier; private _actual: ExtHostConfigProvider | null; constructor( @IExtHostRpcService extHostRpc: IExtHostRpcService, - @IExtHostWorkspace extHostWorkspace: IExtHostWorkspace + @IExtHostWorkspace extHostWorkspace: IExtHostWorkspace, + @ILogService logService: ILogService, ) { this._proxy = extHostRpc.getProxy(MainContext.MainThreadConfiguration); this._extHostWorkspace = extHostWorkspace; + this._logService = logService; this._barrier = new Barrier(); this._actual = null; } @@ -64,7 +68,7 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { } $initializeConfiguration(data: IConfigurationInitData): void { - this._actual = new ExtHostConfigProvider(this._proxy, this._extHostWorkspace, data); + this._actual = new ExtHostConfigProvider(this._proxy, this._extHostWorkspace, data, this._logService); this._barrier.open(); } @@ -80,9 +84,11 @@ export class ExtHostConfigProvider { private readonly _extHostWorkspace: ExtHostWorkspace; private _configurationScopes: Map; private _configuration: Configuration; + private _logService: ILogService; - constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationInitData) { + constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace, data: IConfigurationInitData, logService: ILogService) { this._proxy = proxy; + this._logService = logService; this._extHostWorkspace = extHostWorkspace; this._configuration = ExtHostConfigProvider.parse(data); this._configurationScopes = this._toMap(data.configurationScopes); @@ -236,13 +242,13 @@ export class ExtHostConfigProvider { const extensionIdText = extensionId ? `[${extensionId.value}] ` : ''; if (ConfigurationScope.RESOURCE === scope) { if (resource === undefined) { - console.warn(`${extensionIdText}Accessing a resource scoped configuration without providing a resource is not expected. To get the effective value for '${key}', provide the URI of a resource or 'null' for any resource.`); + this._logService.warn(`${extensionIdText}Accessing a resource scoped configuration without providing a resource is not expected. To get the effective value for '${key}', provide the URI of a resource or 'null' for any resource.`); } return; } if (ConfigurationScope.WINDOW === scope) { if (resource) { - console.warn(`${extensionIdText}Accessing a window scoped configuration for a resource is not expected. To associate '${key}' to a resource, define its scope to 'resource' in configuration contributions in 'package.json'.`); + this._logService.warn(`${extensionIdText}Accessing a window scoped configuration for a resource is not expected. To associate '${key}' to a resource, define its scope to 'resource' in configuration contributions in 'package.json'.`); } return; } diff --git a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts index f319881a103..672c28dd637 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -35,7 +35,7 @@ suite('ExtHostConfiguration', function () { if (!shape) { shape = new class extends mock() { }; } - return new ExtHostConfigProvider(shape, createExtHostWorkspace(), createConfigurationData(contents)); + return new ExtHostConfigProvider(shape, createExtHostWorkspace(), createConfigurationData(contents), new NullLogService()); } function createConfigurationData(contents: any): IConfigurationInitData { @@ -283,7 +283,8 @@ suite('ExtHostConfiguration', function () { workspace: new ConfigurationModel({}, []), folders: [], configurationScopes: [] - } + }, + new NullLogService() ); let actual = testObject.getConfiguration().inspect('editor.wordWrap')!; @@ -331,7 +332,8 @@ suite('ExtHostConfiguration', function () { workspace, folders, configurationScopes: [] - } + }, + new NullLogService() ); let actual1 = testObject.getConfiguration().inspect('editor.wordWrap')!; @@ -407,7 +409,8 @@ suite('ExtHostConfiguration', function () { workspace, folders, configurationScopes: [] - } + }, + new NullLogService() ); let actual1 = testObject.getConfiguration().inspect('editor.wordWrap')!; @@ -607,7 +610,8 @@ suite('ExtHostConfiguration', function () { 'config': false, 'updatedconfig': false } - }) + }), + new NullLogService() ); const newConfigData = createConfigurationData({ From 67ac10ad2879cab015ded52f9e57d96e8a3b471b Mon Sep 17 00:00:00 2001 From: Andre Weinand Date: Mon, 11 Nov 2019 10:55:58 +0100 Subject: [PATCH 18/49] #84283 use log service --- .../contrib/debug/browser/extensionHostDebugService.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index 6283cda2f78..d9b5232c0eb 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -18,6 +18,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IWorkspaceProvider, IWorkspace } from 'vs/workbench/services/host/browser/browserHostService'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; +import { ILogService } from 'vs/platform/log/common/log'; class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient implements IExtensionHostDebugService { @@ -25,7 +26,8 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @ILogService logService: ILogService ) { const connection = remoteAgentService.getConnection(); let channel: IChannel; @@ -34,7 +36,7 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i } else { channel = { call: async () => undefined, listen: () => Event.None } as any; // TODO@weinand TODO@isidorn fallback? - console.warn('Extension Host Debugging not available due to missing connection.'); + logService.warn('Extension Host Debugging not available due to missing connection.'); } super(channel); @@ -43,7 +45,7 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient i this.workspaceProvider = environmentService.options.workspaceProvider; } else { this.workspaceProvider = { open: async () => undefined, workspace: undefined }; - console.warn('Extension Host Debugging not available due to missing workspace provider.'); + logService.warn('Extension Host Debugging not available due to missing workspace provider.'); } // Reload window on reload request From dae6ded35d9e64ddb0589b50d6b30923cefb5a79 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 11 Nov 2019 11:00:17 +0100 Subject: [PATCH 19/49] Update commands.yml --- .github/commands.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/commands.yml b/.github/commands.yml index ca4b782002f..c649e6c4d89 100644 --- a/.github/commands.yml +++ b/.github/commands.yml @@ -96,7 +96,7 @@ { type: 'comment', name: 'confirmationPending', - allowUsers: ['cleidigh', 'usernamehw'], + allowUsers: ['cleidigh', 'usernamehw', 'gjsjohnmurray', 'IllusionMH'], action: 'updateLabels', addLabel: 'confirmation-pending', removeLabel: 'confirmed' From 2a3f65c54f2992c114916e8c1af51f23c3d732c5 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 11 Nov 2019 11:06:50 +0100 Subject: [PATCH 20/49] peek - fix bad ident provider --- src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts index 0db277b7cf1..1b93c77febb 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesTree.ts @@ -101,7 +101,7 @@ export class StringRepresentationProvider implements IKeyboardNavigationLabelPro export class IdentityProvider implements IIdentityProvider { getId(element: TreeElement): { toString(): string; } { - return element.uri; + return element instanceof OneReference ? element.id : element.uri; } } From 69f2f05a37f2ddcbc93d5739373015a821a105b4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 11 Nov 2019 11:09:54 +0100 Subject: [PATCH 21/49] Fix #84436 --- .../workbench/services/keybinding/common/keybindingEditing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts index 8e6059322b1..98c4d011305 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts @@ -250,7 +250,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding private parse(model: ITextModel): { result: IUserFriendlyKeybinding[], parseErrors: json.ParseError[] } { const parseErrors: json.ParseError[] = []; - const result = json.parse(model.getValue(), parseErrors, { allowEmptyContent: true }); + const result = json.parse(model.getValue(), parseErrors, { allowTrailingComma: true, allowEmptyContent: true }); return { result, parseErrors }; } From 5b40229334bacac925a8cd71fe4a5f1b09803855 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 11 Nov 2019 11:29:21 +0100 Subject: [PATCH 22/49] Fix #84457 --- .../test/electron-browser/extensionsTipsService.test.ts | 5 +++-- .../electron-browser/extensionEnablementService.test.ts | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 6a381ae5127..3324f94e082 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -501,6 +501,7 @@ suite('ExtensionsTipsService Test', () => { const ignoredExtensionId = 'Some.Extension'; instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: boolean) => a === 'extensionsAssistant/ignored_recommendations' ? '["ms-vscode.vscode"]' : c, + getBoolean: (a: string, b: StorageScope, c: boolean) => c, store: (...args: any[]) => { storageSetterTarget(...args); } @@ -519,7 +520,7 @@ suite('ExtensionsTipsService Test', () => { test('ExtensionTipsService: Get file based recommendations from storage (old format)', () => { const storedRecommendations = '["ms-vscode.csharp", "ms-python.python", "ms-vscode.vscode-typescript-tslint-plugin"]'; - instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c }); + instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c, getBoolean: (a: string, b: StorageScope, c: boolean) => c }); return setUpFolderWorkspace('myFolder', []).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); @@ -538,7 +539,7 @@ suite('ExtensionsTipsService Test', () => { const now = Date.now(); const tenDaysOld = 10 * milliSecondsInADay; const storedRecommendations = `{"ms-vscode.csharp": ${now}, "ms-python.python": ${now}, "ms-vscode.vscode-typescript-tslint-plugin": ${now}, "lukehoban.Go": ${tenDaysOld}}`; - instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c }); + instantiationService.stub(IStorageService, >{ get: (a: string, b: StorageScope, c?: string) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c, getBoolean: (a: string, b: StorageScope, c: boolean) => c }); return setUpFolderWorkspace('myFolder', []).then(() => { testObject = instantiationService.createInstance(ExtensionTipsService); diff --git a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts index 93bc95313b8..0e4e725ae64 100644 --- a/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts +++ b/src/vs/workbench/services/extensionManagement/test/electron-browser/extensionEnablementService.test.ts @@ -39,13 +39,15 @@ function storageService(instantiationService: TestInstantiationService): IStorag export class TestExtensionEnablementService extends ExtensionEnablementService { constructor(instantiationService: TestInstantiationService) { + const extensionManagementService = instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, { onDidInstallExtension: new Emitter().event, onDidUninstallExtension: new Emitter().event } as IExtensionManagementService); + const extensionManagementServerService = instantiationService.get(IExtensionManagementServerService) || instantiationService.stub(IExtensionManagementServerService, { localExtensionManagementServer: { extensionManagementService } }); super( storageService(instantiationService), instantiationService.get(IWorkspaceContextService), instantiationService.get(IWorkbenchEnvironmentService) || instantiationService.stub(IWorkbenchEnvironmentService, { configuration: Object.create(null) } as IWorkbenchEnvironmentService), - instantiationService.get(IExtensionManagementService) || instantiationService.stub(IExtensionManagementService, - { onDidInstallExtension: new Emitter().event, onDidUninstallExtension: new Emitter().event } as IExtensionManagementService), - instantiationService.get(IConfigurationService), instantiationService.get(IExtensionManagementServerService), + extensionManagementService, + instantiationService.get(IConfigurationService), + extensionManagementServerService, productService ); } From 545e066ccdd0f083dfb6841c0ec1151f19ec4954 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 11 Nov 2019 11:55:04 +0100 Subject: [PATCH 23/49] fixes #83998 --- src/bootstrap.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/bootstrap.js b/src/bootstrap.js index c3815b745cc..b035adc9f41 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -263,7 +263,12 @@ exports.configurePortable = function () { } if (isTempPortable) { - process.env[process.platform === 'win32' ? 'TEMP' : 'TMPDIR'] = portableTempPath; + if (process.platform === 'win32') { + process.env['TMP'] = portableTempPath; + process.env['TEMP'] = portableTempPath; + } else { + process.env['TMPDIR'] = portableTempPath; + } } return { From 8879cf69cd6f363c3f14c917976cad02246943b7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 11 Nov 2019 11:56:06 +0100 Subject: [PATCH 24/49] fix #84352 --- .../contrib/outline/browser/outlinePanel.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts index bf6ab98899a..75777a0e8e7 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts @@ -466,10 +466,14 @@ export class OutlinePanel extends ViewletPanel { } const textModel = editor.getModel(); - const loadingMessage = oldModel && new TimeoutTimer( - () => this._showMessage(localize('loading', "Loading document symbols for '{0}'...", basename(textModel.uri))), - 100 - ); + + let loadingMessage: IDisposable | undefined; + if (!oldModel) { + loadingMessage = new TimeoutTimer( + () => this._showMessage(localize('loading', "Loading document symbols for '{0}'...", basename(textModel.uri))), + 100 + ); + } const requestDelay = OutlineModel.getRequestDelay(textModel); this._progressBar.infinite().show(requestDelay); From 8e64adbbde644b623cf147763caa7ff8471396d0 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 11 Nov 2019 12:22:55 +0100 Subject: [PATCH 25/49] use LogService in extHostLanguageFeatures and extHostMessageService, #84283 --- .../workbench/api/common/extHost.api.impl.ts | 2 +- .../api/common/extHostLanguageFeatures.ts | 49 +++++++++---------- .../api/common/extHostMessageService.ts | 8 ++- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 178d54dd3e0..26ef1db530a 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -130,7 +130,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // Other instances const extHostClipboard = new ExtHostClipboard(rpcProtocol); - const extHostMessageService = new ExtHostMessageService(rpcProtocol); + const extHostMessageService = new ExtHostMessageService(rpcProtocol, extHostLogService); const extHostDialogs = new ExtHostDialogs(rpcProtocol); const extHostStatusBar = new ExtHostStatusBar(rpcProtocol); const extHostLanguages = new ExtHostLanguages(rpcProtocol, extHostDocuments); diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index ee4043d7860..fb15773114e 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -474,11 +474,11 @@ class NavigateTypeAdapter { private readonly _symbolCache = new Map(); private readonly _resultCache = new Map(); - private readonly _provider: vscode.WorkspaceSymbolProvider; - constructor(provider: vscode.WorkspaceSymbolProvider) { - this._provider = provider; - } + constructor( + private readonly _provider: vscode.WorkspaceSymbolProvider, + private readonly _logService: ILogService + ) { } provideWorkspaceSymbols(search: string, token: CancellationToken): Promise { const result: extHostProtocol.IWorkspaceSymbolsDto = extHostProtocol.IdObject.mixin({ symbols: [] }); @@ -490,7 +490,7 @@ class NavigateTypeAdapter { continue; } if (!item.name) { - console.warn('INVALID SymbolInformation, lacks name', item); + this._logService.warn('INVALID SymbolInformation, lacks name', item); continue; } const symbol = extHostProtocol.IdObject.mixin(typeConvert.WorkspaceSymbol.from(item)); @@ -538,7 +538,8 @@ class RenameAdapter { constructor( private readonly _documents: ExtHostDocuments, - private readonly _provider: vscode.RenameProvider + private readonly _provider: vscode.RenameProvider, + private readonly _logService: ILogService ) { } provideRenameEdits(resource: URI, position: IPosition, newName: string, token: CancellationToken): Promise { @@ -587,7 +588,7 @@ class RenameAdapter { return undefined; } if (range.start.line > pos.line || range.end.line < pos.line) { - console.warn('INVALID rename location: position line must be within range start/end lines'); + this._logService.warn('INVALID rename location: position line must be within range start/end lines'); return undefined; } return { range: typeConvert.Range.from(range), text }; @@ -618,18 +619,15 @@ class SuggestAdapter { return typeof provider.resolveCompletionItem === 'function'; } - private _documents: ExtHostDocuments; - private _commands: CommandsConverter; - private _provider: vscode.CompletionItemProvider; - private _cache = new Cache('CompletionItem'); private _disposables = new Map(); - constructor(documents: ExtHostDocuments, commands: CommandsConverter, provider: vscode.CompletionItemProvider) { - this._documents = documents; - this._commands = commands; - this._provider = provider; - } + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _commands: CommandsConverter, + private readonly _provider: vscode.CompletionItemProvider, + private readonly _logService: ILogService + ) { } provideCompletionItems(resource: URI, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { @@ -712,7 +710,7 @@ class SuggestAdapter { private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, id: extHostProtocol.ChainedCacheId): extHostProtocol.ISuggestDataDto | undefined { if (typeof item.label !== 'string' || item.label.length === 0) { - console.warn('INVALID text edit -> must have at least a label'); + this._logService.warn('INVALID text edit -> must have at least a label'); return undefined; } @@ -764,7 +762,7 @@ class SuggestAdapter { if (range) { if (Range.isRange(range)) { if (!SuggestAdapter._isValidRangeForCompletion(range, position)) { - console.trace('INVALID range -> must be single line and on the same line'); + this._logService.trace('INVALID range -> must be single line and on the same line'); return undefined; } result[extHostProtocol.ISuggestDataDtoField.range] = typeConvert.Range.from(range); @@ -776,7 +774,7 @@ class SuggestAdapter { || !range.inserting.start.isEqual(range.replacing.start) || !range.replacing.contains(range.inserting) ) { - console.trace('INVALID range -> must be single line, on the same line, insert range must be a prefix of replace range'); + this._logService.trace('INVALID range -> must be single line, on the same line, insert range must be a prefix of replace range'); return undefined; } result[extHostProtocol.ISuggestDataDtoField.range] = { @@ -992,7 +990,8 @@ class SelectionRangeAdapter { constructor( private readonly _documents: ExtHostDocuments, - private readonly _provider: vscode.SelectionRangeProvider + private readonly _provider: vscode.SelectionRangeProvider, + private readonly _logService: ILogService ) { } provideSelectionRanges(resource: URI, pos: IPosition[], token: CancellationToken): Promise { @@ -1004,7 +1003,7 @@ class SelectionRangeAdapter { return []; } if (allProviderRanges.length !== positions.length) { - console.warn('BAD selection ranges, provider must return ranges for each position'); + this._logService.warn('BAD selection ranges, provider must return ranges for each position'); return []; } @@ -1413,7 +1412,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- navigate types registerWorkspaceSymbolProvider(extension: IExtensionDescription, provider: vscode.WorkspaceSymbolProvider): vscode.Disposable { - const handle = this._addNewAdapter(new NavigateTypeAdapter(provider), extension); + const handle = this._addNewAdapter(new NavigateTypeAdapter(provider, this._logService), extension); this._proxy.$registerNavigateTypeSupport(handle); return this._createDisposable(handle); } @@ -1433,7 +1432,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- rename registerRenameProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.RenameProvider): vscode.Disposable { - const handle = this._addNewAdapter(new RenameAdapter(this._documents, provider), extension); + const handle = this._addNewAdapter(new RenameAdapter(this._documents, provider, this._logService), extension); this._proxy.$registerRenameSupport(handle, this._transformDocumentSelector(selector), RenameAdapter.supportsResolving(provider)); return this._createDisposable(handle); } @@ -1449,7 +1448,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- suggestion registerCompletionItemProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable { - const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider), extension); + const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider, this._logService), extension); this._proxy.$registerSuggestSupport(handle, this._transformDocumentSelector(selector), triggerCharacters, SuggestAdapter.supportsResolving(provider), extension.identifier); return this._createDisposable(handle); } @@ -1533,7 +1532,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF // --- smart select registerSelectionRangeProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SelectionRangeProvider): vscode.Disposable { - const handle = this._addNewAdapter(new SelectionRangeAdapter(this._documents, provider), extension); + const handle = this._addNewAdapter(new SelectionRangeAdapter(this._documents, provider, this._logService), extension); this._proxy.$registerSelectionRangeProvider(handle, this._transformDocumentSelector(selector)); return this._createDisposable(handle); } diff --git a/src/vs/workbench/api/common/extHostMessageService.ts b/src/vs/workbench/api/common/extHostMessageService.ts index 997ca16271f..31154ab1de9 100644 --- a/src/vs/workbench/api/common/extHostMessageService.ts +++ b/src/vs/workbench/api/common/extHostMessageService.ts @@ -7,6 +7,7 @@ import Severity from 'vs/base/common/severity'; import * as vscode from 'vscode'; import { MainContext, MainThreadMessageServiceShape, MainThreadMessageOptions, IMainContext } from './extHost.protocol'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; function isMessageItem(item: any): item is vscode.MessageItem { return item && item.title; @@ -16,7 +17,10 @@ export class ExtHostMessageService { private _proxy: MainThreadMessageServiceShape; - constructor(mainContext: IMainContext) { + constructor( + mainContext: IMainContext, + @ILogService private readonly _logService: ILogService + ) { this._proxy = mainContext.getProxy(MainContext.MainThreadMessageService); } @@ -45,7 +49,7 @@ export class ExtHostMessageService { let { title, isCloseAffordance } = command; commands.push({ title, isCloseAffordance: !!isCloseAffordance, handle }); } else { - console.warn('Invalid message item:', command); + this._logService.warn('Invalid message item:', command); } } From 336ff850c455c6182cdc7b63a45a79043c8264c0 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 11 Nov 2019 12:26:53 +0100 Subject: [PATCH 26/49] Fix #82714 --- .../workbench/contrib/preferences/browser/keybindingWidgets.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index 4002fe6e377..0bfccbd8aca 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -82,7 +82,7 @@ export class KeybindingsSearchWidget extends SearchWidget { stopRecordingKeys(): void { this._reset(); - this.recordDisposables.dispose(); + this.recordDisposables.clear(); } setInputValue(value: string): void { From e74086a39bd14b2a770b56383c3d4be88f4cf428 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 11 Nov 2019 12:33:02 +0100 Subject: [PATCH 27/49] peek - use meta title to classify contents --- .../editor/contrib/gotoSymbol/goToCommands.ts | 43 +++---------------- .../gotoSymbol/peek/referencesController.ts | 6 +-- .../contrib/gotoSymbol/referencesModel.ts | 10 ++++- .../gotoSymbol/test/referencesModel.test.ts | 2 +- 4 files changed, 18 insertions(+), 43 deletions(-) diff --git a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts index 8fab75a9709..5ec0555a5ef 100644 --- a/src/vs/editor/contrib/gotoSymbol/goToCommands.ts +++ b/src/vs/editor/contrib/gotoSymbol/goToCommands.ts @@ -107,8 +107,6 @@ abstract class SymbolNavigationAction extends EditorAction { protected abstract _getNoResultFoundMessage(info: IWordAtPosition | null): string; - protected abstract _getMetaTitle(model: ReferencesModel): string; - protected abstract _getAlternativeCommand(): string; protected abstract _getGoToPreference(editor: IActiveCodeEditor): GoToLocationValues; @@ -171,7 +169,7 @@ abstract class SymbolNavigationAction extends EditorAction { export class DefinitionAction extends SymbolNavigationAction { protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return new ReferencesModel(await getDefinitionsAtPosition(model, position, token)); + return new ReferencesModel(await getDefinitionsAtPosition(model, position, token), nls.localize('def.title', 'Definitions')); } protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { @@ -180,10 +178,6 @@ export class DefinitionAction extends SymbolNavigationAction { : nls.localize('generic.noResults', "No definition found"); } - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('meta.title', " – {0} definitions", model.references.length) : ''; - } - protected _getAlternativeCommand(): string { return 'editor.action.goToReferences'; } @@ -295,7 +289,7 @@ registerEditorAction(class PeekDefinitionAction extends DefinitionAction { class DeclarationAction extends SymbolNavigationAction { protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return new ReferencesModel(await getDeclarationsAtPosition(model, position, token)); + return new ReferencesModel(await getDeclarationsAtPosition(model, position, token), nls.localize('decl.title', 'Declarations')); } protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { @@ -304,10 +298,6 @@ class DeclarationAction extends SymbolNavigationAction { : nls.localize('decl.generic.noResults', "No declaration found"); } - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('decl.meta.title', " – {0} declarations", model.references.length) : ''; - } - protected _getAlternativeCommand(): string { return 'editor.action.goToReferences'; } @@ -352,10 +342,6 @@ registerEditorAction(class GoToDeclarationAction extends DeclarationAction { ? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word) : nls.localize('decl.generic.noResults', "No declaration found"); } - - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('decl.meta.title', " – {0} declarations", model.references.length) : ''; - } }); registerEditorAction(class PeekDeclarationAction extends DeclarationAction { @@ -384,7 +370,7 @@ registerEditorAction(class PeekDeclarationAction extends DeclarationAction { class TypeDefinitionAction extends SymbolNavigationAction { protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return new ReferencesModel(await getTypeDefinitionsAtPosition(model, position, token)); + return new ReferencesModel(await getTypeDefinitionsAtPosition(model, position, token), nls.localize('typedef.title', 'Type Definitions')); } protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { @@ -393,10 +379,6 @@ class TypeDefinitionAction extends SymbolNavigationAction { : nls.localize('goToTypeDefinition.generic.noResults', "No type definition found"); } - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('meta.typeDefinitions.title', " – {0} type definitions", model.references.length) : ''; - } - protected _getAlternativeCommand(): string { return 'editor.action.goToReferences'; } @@ -470,7 +452,7 @@ registerEditorAction(class PeekTypeDefinitionAction extends TypeDefinitionAction class ImplementationAction extends SymbolNavigationAction { protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return new ReferencesModel(await getImplementationsAtPosition(model, position, token)); + return new ReferencesModel(await getImplementationsAtPosition(model, position, token), nls.localize('impl.title', 'Implementations')); } protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { @@ -479,10 +461,6 @@ class ImplementationAction extends SymbolNavigationAction { : nls.localize('goToImplementation.generic.noResults', "No implementation found"); } - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 ? nls.localize('meta.implementations.title', " – {0} implementations", model.references.length) : ''; - } - protected _getAlternativeCommand(): string { return ''; } @@ -561,7 +539,7 @@ registerEditorAction(class PeekImplementationAction extends ImplementationAction class ReferencesAction extends SymbolNavigationAction { protected async _getLocationModel(model: ITextModel, position: corePosition.Position, token: CancellationToken): Promise { - return new ReferencesModel(await getReferencesAtPosition(model, position, token)); + return new ReferencesModel(await getReferencesAtPosition(model, position, token), nls.localize('ref.title', 'References')); } protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { @@ -570,12 +548,6 @@ class ReferencesAction extends SymbolNavigationAction { : nls.localize('references.noGeneric', "No references found"); } - protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 - ? nls.localize('meta.titleReference', " – {0} references", model.references.length) - : ''; - } - protected _getAlternativeCommand(): string { return ''; } @@ -667,7 +639,7 @@ class GenericGoToLocationAction extends SymbolNavigationAction { } protected async _getLocationModel(_model: ITextModel, _position: corePosition.Position, _token: CancellationToken): Promise { - return new ReferencesModel(this._references); + return new ReferencesModel(this._references, nls.localize('generic.title', 'Locations')); } protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { @@ -678,7 +650,6 @@ class GenericGoToLocationAction extends SymbolNavigationAction { return this._gotoMultipleBehaviour ?? editor.getOption(EditorOption.gotoLocation).multipleReferences; } - protected _getMetaTitle() { return ''; } protected _getAlternativeCommand() { return ''; } } @@ -736,7 +707,7 @@ CommandsRegistry.registerCommand({ return undefined; } - const references = createCancelablePromise(token => getReferencesAtPosition(control.getModel(), corePosition.Position.lift(position), token).then(references => new ReferencesModel(references))); + const references = createCancelablePromise(token => getReferencesAtPosition(control.getModel(), corePosition.Position.lift(position), token).then(references => new ReferencesModel(references, nls.localize('ref.title', 'References')))); const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); return Promise.resolve(controller.toggleWidget(range, references, false)); }); diff --git a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts index aaaede8378e..4d22101b994 100644 --- a/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts +++ b/src/vs/editor/contrib/gotoSymbol/peek/referencesController.ts @@ -151,10 +151,8 @@ export abstract class ReferencesController implements editorCommon.IEditorContri if (this._widget && this._model && this._editor.hasModel()) { // might have been closed // set title - if (this._model.references.length === 1) { - this._widget.setMetaTitle(nls.localize('metaTitle.1', "1 result")); - } else if (!this._model.isEmpty) { - this._widget.setMetaTitle(nls.localize('metaTitle.N', "{0} results", this._model.references.length)); + if (!this._model.isEmpty) { + this._widget.setMetaTitle(nls.localize('metaTitle.N', "{0} ({1})", this._model.title, this._model.references.length)); } else { this._widget.setMetaTitle(''); } diff --git a/src/vs/editor/contrib/gotoSymbol/referencesModel.ts b/src/vs/editor/contrib/gotoSymbol/referencesModel.ts index 77b09ed7b0f..fa0f2c14db2 100644 --- a/src/vs/editor/contrib/gotoSymbol/referencesModel.ts +++ b/src/vs/editor/contrib/gotoSymbol/referencesModel.ts @@ -149,6 +149,7 @@ export class ReferencesModel implements IDisposable { private readonly _disposables = new DisposableStore(); private readonly _links: LocationLink[]; + private readonly _title: string; readonly groups: FileReferences[] = []; readonly references: OneReference[] = []; @@ -156,8 +157,9 @@ export class ReferencesModel implements IDisposable { readonly _onDidChangeReferenceRange = new Emitter(); readonly onDidChangeReferenceRange: Event = this._onDidChangeReferenceRange.event; - constructor(links: LocationLink[]) { + constructor(links: LocationLink[], title: string) { this._links = links; + this._title = title; // grouping and sorting const [providersFirst] = links; @@ -192,7 +194,11 @@ export class ReferencesModel implements IDisposable { } clone(): ReferencesModel { - return new ReferencesModel(this._links); + return new ReferencesModel(this._links, this._title); + } + + get title(): string { + return this._title; } get isEmpty(): boolean { diff --git a/src/vs/editor/contrib/gotoSymbol/test/referencesModel.test.ts b/src/vs/editor/contrib/gotoSymbol/test/referencesModel.test.ts index f3a0d011914..c2a1b95246a 100644 --- a/src/vs/editor/contrib/gotoSymbol/test/referencesModel.test.ts +++ b/src/vs/editor/contrib/gotoSymbol/test/referencesModel.test.ts @@ -21,7 +21,7 @@ suite('references', function () { }, { uri: URI.file('/src/can'), range: new Range(1, 1, 1, 1) - }]); + }], 'FOO'); let ref = model.nearestReference(URI.file('/src/can'), new Position(1, 1)); assert.equal(ref!.uri.path, '/src/can'); From 3c610a66d6551b35dda11877ed5d7d037d2d8a55 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 11 Nov 2019 13:57:52 +0100 Subject: [PATCH 28/49] disable test custom editor for now --- .../browser/testCustomEditors.ts | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts b/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts index 6bb83b86b55..6f25031e1db 100644 --- a/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts +++ b/src/vs/workbench/contrib/testCustomEditors/browser/testCustomEditors.ts @@ -25,7 +25,9 @@ import { generateUuid } from 'vs/base/common/uuid'; import { CancellationToken } from 'vs/base/common/cancellation'; import { editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; -export class TestCustomEditorsAction extends Action { +const ENABLE = false; + +class TestCustomEditorsAction extends Action { static readonly ID = 'workbench.action.openCustomEditor'; static readonly LABEL = nls.localize('openCustomEditor', "Test Open Custom Editor"); @@ -45,7 +47,7 @@ export class TestCustomEditorsAction extends Action { } } -export class TestCustomEditor extends BaseEditor { +class TestCustomEditor extends BaseEditor { static ID = 'testCustomEditor'; @@ -109,7 +111,7 @@ export class TestCustomEditor extends BaseEditor { layout(dimension: Dimension): void { } } -export class TestCustomEditorInput extends EditorInput { +class TestCustomEditorInput extends EditorInput { private model: TestCustomEditorModel | undefined = undefined; private dirty = false; @@ -176,7 +178,7 @@ export class TestCustomEditorInput extends EditorInput { } } -export class TestCustomEditorModel extends EditorModel { +class TestCustomEditorModel extends EditorModel { public value: string = ''; @@ -185,32 +187,34 @@ export class TestCustomEditorModel extends EditorModel { } } -Registry.as(EditorExtensions.Editors).registerEditor( - new EditorDescriptor( - TestCustomEditor, - TestCustomEditor.ID, - nls.localize('testCustomEditor', "Test Custom Editor") - ), - [ - new SyncDescriptor(TestCustomEditorInput), - ] -); +if (ENABLE) { + Registry.as(EditorExtensions.Editors).registerEditor( + new EditorDescriptor( + TestCustomEditor, + TestCustomEditor.ID, + nls.localize('testCustomEditor', "Test Custom Editor") + ), + [ + new SyncDescriptor(TestCustomEditorInput), + ] + ); -const registry = Registry.as(Extensions.WorkbenchActions); + const registry = Registry.as(Extensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(TestCustomEditorsAction, TestCustomEditorsAction.ID, TestCustomEditorsAction.LABEL), 'Test Open Custom Editor'); + registry.registerWorkbenchAction(new SyncActionDescriptor(TestCustomEditorsAction, TestCustomEditorsAction.ID, TestCustomEditorsAction.LABEL), 'Test Open Custom Editor'); -class TestCustomEditorInputFactory implements IEditorInputFactory { + class TestCustomEditorInputFactory implements IEditorInputFactory { - serialize(editorInput: TestCustomEditorInput): string { - return JSON.stringify({ - resource: editorInput.resource.toString() - }); + serialize(editorInput: TestCustomEditorInput): string { + return JSON.stringify({ + resource: editorInput.resource.toString() + }); + } + + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): TestCustomEditorInput { + return new TestCustomEditorInput(URI.parse(JSON.parse(serializedEditorInput).resource)); + } } - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): TestCustomEditorInput { - return new TestCustomEditorInput(URI.parse(JSON.parse(serializedEditorInput).resource)); - } + Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(TestCustomEditor.ID, TestCustomEditorInputFactory); } - -Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(TestCustomEditor.ID, TestCustomEditorInputFactory); From 9cb1cb506fd8e2b52898b3110cc6a36e2eb1ff9e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 11 Nov 2019 14:22:24 +0100 Subject: [PATCH 29/49] #78168 Fix strict null checks --- .../extensions/browser/extensionEditor.ts | 6 +- .../extensions/browser/extensionsActions.ts | 147 +++++++++++------- 2 files changed, 91 insertions(+), 62 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 7c0193db0ec..e6681ffe074 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -452,10 +452,8 @@ export class ExtensionEditor extends BaseEditor { private setSubText(extension: IExtension, reloadAction: ReloadAction, template: IExtensionEditorTemplate): void { hide(template.subtextContainer); - const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction); - const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction); - ignoreAction.extension = extension; - undoIgnoreAction.extension = extension; + const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction, extension); + const undoIgnoreAction = this.instantiationService.createInstance(UndoIgnoreExtensionRecommendationAction, extension); ignoreAction.enabled = false; undoIgnoreAction.enabled = false; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 1d8099e1d2f..2b847bf7ab5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -135,9 +135,9 @@ function getRelativeDateLabel(date: Date): string { } export abstract class ExtensionAction extends Action implements IExtensionContainer { - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { this._extension = extension; this.update(); } + private _extension: IExtension | null = null; + get extension(): IExtension | null { return this._extension; } + set extension(extension: IExtension | null) { this._extension = extension; this.update(); } abstract update(): void; } @@ -150,7 +150,7 @@ export class InstallAction extends ExtensionAction { private static readonly InstallingClass = 'extension-action install installing'; - private _manifest: IExtensionManifest | null; + private _manifest: IExtensionManifest | null = null; set manifest(manifest: IExtensionManifest) { this._manifest = manifest; this.updateLabel(); @@ -193,6 +193,9 @@ export class InstallAction extends ExtensionAction { } private updateLabel(): void { + if (!this.extension) { + return; + } if (this.extension.state === ExtensionState.Installing) { this.label = InstallAction.INSTALLING_LABEL; this.tooltip = InstallAction.INSTALLING_LABEL; @@ -214,6 +217,9 @@ export class InstallAction extends ExtensionAction { } async run(): Promise { + if (!this.extension) { + return; + } this.extensionsWorkbenchService.open(this.extension); alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); @@ -303,7 +309,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { // disabled by extension kind or it is a language pack extension && (this.extension.enablementState === EnablementState.DisabledByExtensionKind || isLanguagePackExtension(this.extension.local.manifest)) ) { - const extensionInOtherServer = this.extensionsWorkbenchService.installed.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === this.server)[0]; + const extensionInOtherServer = this.extensionsWorkbenchService.installed.filter(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server === this.server)[0]; if (extensionInOtherServer) { // Getting installed in other server if (extensionInOtherServer.state === ExtensionState.Installing && !extensionInOtherServer.local) { @@ -320,6 +326,9 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { } async run(): Promise { + if (!this.extension) { + return; + } if (this.server) { this.extensionsWorkbenchService.open(this.extension); alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); @@ -412,11 +421,14 @@ export class UninstallAction extends ExtensionAction { this.enabled = true; } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } alert(localize('uninstallExtensionStart', "Uninstalling extension {0} started.", this.extension.displayName)); return this.extensionsWorkbenchService.uninstall(this.extension).then(() => { - alert(localize('uninstallExtensionComplete', "Please reload Visual Studio Code to complete the uninstallation of the extension {0}.", this.extension.displayName)); + alert(localize('uninstallExtensionComplete', "Please reload Visual Studio Code to complete the uninstallation of the extension {0}.", this.extension!.displayName)); }); } } @@ -527,14 +539,17 @@ export class UpdateAction extends ExtensionAction { this.label = this.extension.outdated ? this.getUpdateLabel(this.extension.latestVersion) : this.getUpdateLabel(); } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } alert(localize('updateExtensionStart', "Updating extension {0} to version {1} started.", this.extension.displayName, this.extension.latestVersion)); return this.install(this.extension); } private install(extension: IExtension): Promise { return this.extensionsWorkbenchService.install(extension).then(() => { - alert(localize('updateExtensionComplete', "Updating extension {0} to version {1} completed.", this.extension.displayName, this.extension.latestVersion)); + alert(localize('updateExtensionComplete', "Updating extension {0} to version {1} completed.", extension.displayName, extension.latestVersion)); }, err => { if (!extension.gallery) { return this.notificationService.error(err); @@ -557,8 +572,6 @@ interface IExtensionActionViewItemOptions extends IActionViewItemOptions { export class ExtensionActionViewItem extends ActionViewItem { - protected options: IExtensionActionViewItemOptions; - constructor(context: any, action: IAction, options: IExtensionActionViewItemOptions = {}) { super(context, action, options); } @@ -566,14 +579,14 @@ export class ExtensionActionViewItem extends ActionViewItem { updateEnabled(): void { super.updateEnabled(); - if (this.label && this.options.tabOnlyOnFocus && this.getAction().enabled && !this._hasFocus) { + if (this.label && (this.options).tabOnlyOnFocus && this.getAction().enabled && !this._hasFocus) { DOM.removeTabIndexAndUpdateFocus(this.label); } } - private _hasFocus: boolean; + private _hasFocus: boolean = false; setFocus(value: boolean): void { - if (!this.options.tabOnlyOnFocus || this._hasFocus === value) { + if (!(this.options).tabOnlyOnFocus || this._hasFocus === value) { return; } this._hasFocus = value; @@ -600,7 +613,7 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { super(id, label, cssClass, enabled); } - private _actionViewItem: DropDownMenuActionViewItem; + private _actionViewItem: DropDownMenuActionViewItem | null = null; createActionViewItem(): DropDownMenuActionViewItem { this._actionViewItem = this.instantiationService.createInstance(DropDownMenuActionViewItem, this, this.tabOnlyOnFocus); return this._actionViewItem; @@ -692,13 +705,14 @@ export class ManageExtensionAction extends ExtensionDropDownAction { groups.push([this.instantiationService.createInstance(UninstallAction)]); groups.push([this.instantiationService.createInstance(InstallAnotherVersionAction)]); - const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(ExtensionInfoAction)]; - if (this.extension.local && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.configuration) { - extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction)); + if (this.extension) { + const extensionActions: ExtensionAction[] = [this.instantiationService.createInstance(ExtensionInfoAction)]; + if (this.extension.local && this.extension.local.manifest.contributes && this.extension.local.manifest.contributes.configuration) { + extensionActions.push(this.instantiationService.createInstance(ExtensionSettingsAction)); + } + groups.push(extensionActions); } - groups.push(extensionActions); - groups.forEach(group => group.forEach(extensionAction => extensionAction.extension = this.extension)); return groups; @@ -742,7 +756,7 @@ export class InstallAnotherVersionAction extends ExtensionAction { } update(): void { - this.enabled = this.extension && !!this.extension.gallery; + this.enabled = !!this.extension && !!this.extension.gallery; } run(): Promise { @@ -752,19 +766,19 @@ export class InstallAnotherVersionAction extends ExtensionAction { return this.quickInputService.pick(this.getVersionEntries(), { placeHolder: localize('selectVersion', "Select Version to Install"), matchOnDetail: true }) .then(pick => { if (pick) { - if (this.extension.version === pick.id) { + if (this.extension!.version === pick.id) { return Promise.resolve(); } - const promise: Promise = pick.latest ? this.extensionsWorkbenchService.install(this.extension) : this.extensionsWorkbenchService.installVersion(this.extension, pick.id); + const promise: Promise = pick.latest ? this.extensionsWorkbenchService.install(this.extension!) : this.extensionsWorkbenchService.installVersion(this.extension!, pick.id); return promise .then(null, err => { - if (!this.extension.gallery) { + if (!this.extension!.gallery) { return this.notificationService.error(err); } console.error(err); - return promptDownloadManually(this.extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", this.extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService, this.productService); + return promptDownloadManually(this.extension!.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", this.extension!.identifier.id), err, this.instantiationService, this.notificationService, this.openerService, this.productService); }); } return null; @@ -772,8 +786,8 @@ export class InstallAnotherVersionAction extends ExtensionAction { } private getVersionEntries(): Promise<(IQuickPickItem & { latest: boolean, id: string })[]> { - return this.extensionGalleryService.getAllVersions(this.extension.gallery!, true) - .then(allVersions => allVersions.map((v, i) => ({ id: v.version, label: v.version, description: `${getRelativeDateLabel(new Date(Date.parse(v.date)))}${v.version === this.extension.version ? ` (${localize('current', "Current")})` : ''}`, latest: i === 0 }))); + return this.extensionGalleryService.getAllVersions(this.extension!.gallery!, true) + .then(allVersions => allVersions.map((v, i) => ({ id: v.version, label: v.version, description: `${getRelativeDateLabel(new Date(Date.parse(v.date)))}${v.version === this.extension!.version ? ` (${localize('current', "Current")})` : ''}`, latest: i === 0 }))); } } @@ -793,7 +807,10 @@ export class ExtensionInfoAction extends ExtensionAction { this.enabled = !!this.extension; } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } const name = localize('extensionInfoName', 'Name: {0}', this.extension.displayName); const id = localize('extensionInfoId', 'Id: {0}', this.extension.identifier.id); @@ -823,7 +840,11 @@ export class ExtensionSettingsAction extends ExtensionAction { update(): void { this.enabled = !!this.extension; } - run(): Promise { + + async run(): Promise { + if (!this.extension) { + return; + } this.preferencesService.openSettings(false, `@ext:${this.extension.identifier.id}`); return Promise.resolve(); } @@ -851,7 +872,10 @@ export class EnableForWorkspaceAction extends ExtensionAction { } } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.EnabledWorkspace); } } @@ -878,7 +902,10 @@ export class EnableGloballyAction extends ExtensionAction { } } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.EnabledGlobally); } } @@ -899,14 +926,17 @@ export class DisableForWorkspaceAction extends ExtensionAction { update(): void { this.enabled = false; - if (this.extension && this.extension.local && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier) && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY)) { + if (this.extension && this.extension.local && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier) && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY)) { this.enabled = this.extension.state === ExtensionState.Installed && (this.extension.enablementState === EnablementState.EnabledGlobally || this.extension.enablementState === EnablementState.EnabledWorkspace) && this.extensionEnablementService.canChangeEnablement(this.extension.local); } } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.DisabledWorkspace); } } @@ -926,14 +956,17 @@ export class DisableGloballyAction extends ExtensionAction { update(): void { this.enabled = false; - if (this.extension && this.extension.local && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))) { + if (this.extension && this.extension.local && this.runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))) { this.enabled = this.extension.state === ExtensionState.Installed && (this.extension.enablementState === EnablementState.EnabledGlobally || this.extension.enablementState === EnablementState.EnabledWorkspace) && this.extensionEnablementService.canChangeEnablement(this.extension.local); } } - run(): Promise { + async run(): Promise { + if (!this.extension) { + return; + } return this.extensionsWorkbenchService.setEnablement(this.extension, EnablementState.DisabledGlobally); } } @@ -1190,11 +1223,11 @@ export class ReloadAction extends ExtensionAction { } private computeReloadState(): void { - if (!this._runningExtensions) { + if (!this._runningExtensions || !this.extension) { return; } const isUninstalled = this.extension.state === ExtensionState.Uninstalled; - const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))[0]; + const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; const isSameExtensionRunning = runningExtension && this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation); if (isUninstalled) { @@ -1242,7 +1275,7 @@ export class ReloadAction extends ExtensionAction { const otherServer = this.extension.server ? this.extension.server === this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer : null; if (otherServer && this.extension.enablementState === EnablementState.DisabledByExtensionKind) { - const extensionInOtherServer = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server === otherServer)[0]; + const extensionInOtherServer = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server === otherServer)[0]; // Same extension in other server exists and if (extensionInOtherServer && extensionInOtherServer.local && this.extensionEnablementService.isEnabled(extensionInOtherServer.local)) { this.enabled = true; @@ -1300,7 +1333,7 @@ export class SetColorThemeAction extends ExtensionAction { if (!this.enabled) { return; } - let extensionThemes = SetColorThemeAction.getColorThemes(this.colorThemes, this.extension); + let extensionThemes = SetColorThemeAction.getColorThemes(this.colorThemes, this.extension!); const currentTheme = this.colorThemes.filter(t => t.settingsId === this.configurationService.getValue(COLOR_THEME_SETTING))[0]; showCurrentTheme = showCurrentTheme || extensionThemes.some(t => t.id === currentTheme.id); if (showCurrentTheme) { @@ -1366,7 +1399,7 @@ export class SetFileIconThemeAction extends ExtensionAction { if (!this.enabled) { return; } - let extensionThemes = SetFileIconThemeAction.getFileIconThemes(this.fileIconThemes, this.extension); + let extensionThemes = SetFileIconThemeAction.getFileIconThemes(this.fileIconThemes, this.extension!); const currentTheme = this.fileIconThemes.filter(t => t.settingsId === this.configurationService.getValue(ICON_THEME_SETTING))[0] || this.workbenchThemeService.getFileIconTheme(); showCurrentTheme = showCurrentTheme || extensionThemes.some(t => t.id === currentTheme.id); if (showCurrentTheme) { @@ -1719,9 +1752,8 @@ export class IgnoreExtensionRecommendationAction extends Action { private static readonly Class = 'extension-action ignore'; - extension: IExtension; - constructor( + private readonly extension: IExtension, @IExtensionTipsService private readonly extensionsTipsService: IExtensionTipsService, ) { super(IgnoreExtensionRecommendationAction.ID, 'Ignore Recommendation'); @@ -1743,9 +1775,8 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { private static readonly Class = 'extension-action undo-ignore'; - extension: IExtension; - constructor( + private readonly extension: IExtension, @IExtensionTipsService private readonly extensionsTipsService: IExtensionTipsService, ) { super(UndoIgnoreExtensionRecommendationAction.ID, 'Undo'); @@ -2389,9 +2420,9 @@ export class StatusLabelAction extends Action implements IExtensionContainer { private status: ExtensionState | null = null; private enablementState: EnablementState | null = null; - private _extension: IExtension; - get extension(): IExtension { return this._extension; } - set extension(extension: IExtension) { + private _extension: IExtension | null = null; + get extension(): IExtension | null { return this._extension; } + set extension(extension: IExtension | null) { if (!(this._extension && extension && areSameExtensions(this._extension.identifier, extension.identifier))) { // Different extension. Reset this.initialStatus = null; @@ -2432,21 +2463,21 @@ export class StatusLabelAction extends Action implements IExtensionContainer { const runningExtensions = await this.extensionService.getExtensions(); const canAddExtension = () => { - const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))[0]; - if (this.extension.local) { - if (runningExtension && this.extension.version === runningExtension.version) { + const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; + if (this.extension!.local) { + if (runningExtension && this.extension!.version === runningExtension.version) { return true; } - return this.extensionService.canAddExtension(toExtensionDescription(this.extension.local)); + return this.extensionService.canAddExtension(toExtensionDescription(this.extension!.local)); } return false; }; const canRemoveExtension = () => { - if (this.extension.local) { - if (runningExtensions.every(e => !(areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier) && this.extension.server === this.extensionManagementServerService.getExtensionManagementServer(e.extensionLocation)))) { + if (this.extension!.local) { + if (runningExtensions.every(e => !(areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier) && this.extension!.server === this.extensionManagementServerService.getExtensionManagementServer(e.extensionLocation)))) { return true; } - return this.extensionService.canRemoveExtension(toExtensionDescription(this.extension.local)); + return this.extensionService.canRemoveExtension(toExtensionDescription(this.extension!.local)); } return false; }; @@ -2549,7 +2580,7 @@ export class ExtensionToolTipAction extends ExtensionAction { return this.warningAction.tooltip; } if (this.extension && this.extension.local && this.extension.state === ExtensionState.Installed && this._runningExtensions) { - const isRunning = this._runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier)); + const isRunning = this._runningExtensions.some(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier)); const isEnabled = this.extensionEnablementService.isEnabled(this.extension.local); if (isEnabled && isRunning) { @@ -2623,7 +2654,7 @@ export class SystemDisabledWarningAction extends ExtensionAction { return; } if (isLanguagePackExtension(this.extension.local.manifest)) { - if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server !== this.extension.server)) { + if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server !== this.extension!.server)) { this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; this.tooltip = this.extension.server === this.extensionManagementServerService.localExtensionManagementServer ? localize('Install language pack also in remote server', "Install the language pack extension on '{0}' to enable it also there.", this.extensionManagementServerService.remoteExtensionManagementServer.label) @@ -2632,13 +2663,13 @@ export class SystemDisabledWarningAction extends ExtensionAction { return; } if (this.extension.enablementState === EnablementState.DisabledByExtensionKind) { - if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension.identifier) && e.server !== this.extension.server)) { + if (!this.extensionsWorkbenchService.installed.some(e => areSameExtensions(e.identifier, this.extension!.identifier) && e.server !== this.extension!.server)) { this.class = `${SystemDisabledWarningAction.WARNING_CLASS}`; const server = this.extensionManagementServerService.localExtensionManagementServer === this.extension.server ? this.extensionManagementServerService.remoteExtensionManagementServer : this.extensionManagementServerService.localExtensionManagementServer; this.tooltip = localize('Install in other server to enable', "Install the extension on '{0}' to enable.", server.label); return; } - const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension.identifier))[0]; + const runningExtension = this._runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, this.extension!.identifier))[0]; const runningExtensionServer = runningExtension ? this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation) : null; if (this.extension.server === this.extensionManagementServerService.localExtensionManagementServer && runningExtensionServer === this.extensionManagementServerService.remoteExtensionManagementServer) { this.class = `${SystemDisabledWarningAction.INFO_CLASS}`; From 3c39263275858211a4c2cb7a8dd9af901a7e976d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 11 Nov 2019 14:37:28 +0100 Subject: [PATCH 30/49] #78168 Strict null check --- .../preferences/browser/keybindingsEditor.ts | 110 +++++++----------- 1 file changed, 45 insertions(+), 65 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 4169b1d34bb..e4b845e14a7 100644 --- a/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -65,22 +65,22 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor private _onLayout: Emitter = this._register(new Emitter()); readonly onLayout: Event = this._onLayout.event; - private keybindingsEditorModel: KeybindingsEditorModel; + private keybindingsEditorModel: KeybindingsEditorModel | null = null; - private headerContainer: HTMLElement; - private actionsContainer: HTMLElement; - private searchWidget: KeybindingsSearchWidget; + private headerContainer!: HTMLElement; + private actionsContainer!: HTMLElement; + private searchWidget!: KeybindingsSearchWidget; - private overlayContainer: HTMLElement; - private defineKeybindingWidget: DefineKeybindingWidget; + private overlayContainer!: HTMLElement; + private defineKeybindingWidget!: DefineKeybindingWidget; private columnItems: ColumnItem[] = []; - private keybindingsListContainer: HTMLElement; - private unAssignedKeybindingItemToRevealAndFocus: IKeybindingItemEntry | null; - private listEntries: IListEntry[]; - private keybindingsList: WorkbenchList; + private keybindingsListContainer!: HTMLElement; + private unAssignedKeybindingItemToRevealAndFocus: IKeybindingItemEntry | null = null; + private listEntries: IListEntry[] = []; + private keybindingsList!: WorkbenchList; - private dimension: DOM.Dimension; + private dimension: DOM.Dimension | null = null; private delayedFiltering: Delayer; private latestEmptyFilters: string[] = []; private delayedFilterLogging: Delayer; @@ -88,11 +88,10 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor private keybindingFocusContextKey: IContextKey; private searchFocusContextKey: IContextKey; - private actionBar: ActionBar; - private sortByPrecedenceAction: Action; - private recordKeysAction: Action; + private readonly sortByPrecedenceAction: Action; + private readonly recordKeysAction: Action; - private ariaLabelElement: HTMLElement; + private ariaLabelElement!: HTMLElement; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -115,6 +114,16 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.searchFocusContextKey = CONTEXT_KEYBINDINGS_SEARCH_FOCUS.bindTo(this.contextKeyService); this.keybindingFocusContextKey = CONTEXT_KEYBINDING_FOCUS.bindTo(this.contextKeyService); this.delayedFilterLogging = new Delayer(1000); + + const recordKeysActionKeybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS); + const recordKeysActionLabel = localize('recordKeysLabel', "Record Keys"); + this.recordKeysAction = new Action(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, recordKeysActionKeybinding ? localize('recordKeysLabelWithKeybinding', "{0} ({1})", recordKeysActionLabel, recordKeysActionKeybinding.getLabel()) : recordKeysActionLabel, 'codicon-record-keys'); + this.recordKeysAction.checked = false; + + const sortByPrecedenceActionKeybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE); + const sortByPrecedenceActionLabel = localize('sortByPrecedeneLabel', "Sort by Precedence"); + this.sortByPrecedenceAction = new Action('keybindings.editor.sortByPrecedence', sortByPrecedenceActionKeybinding ? localize('sortByPrecedeneLabelWithKeybinding', "{0} ({1})", sortByPrecedenceActionLabel, sortByPrecedenceActionKeybinding.getLabel()) : sortByPrecedenceActionLabel, 'codicon-sort-precedence'); + this.sortByPrecedenceAction.checked = false; } createEditor(parent: HTMLElement): void { @@ -298,7 +307,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.overlayContainer.style.position = 'absolute'; this.overlayContainer.style.zIndex = '10'; this.defineKeybindingWidget = this._register(this.instantiationService.createInstance(DefineKeybindingWidget, this.overlayContainer)); - this._register(this.defineKeybindingWidget.onDidChange(keybindingStr => this.defineKeybindingWidget.printExisting(this.keybindingsEditorModel.fetch(`"${keybindingStr}"`).length))); + this._register(this.defineKeybindingWidget.onDidChange(keybindingStr => this.defineKeybindingWidget.printExisting(this.keybindingsEditorModel!.fetch(`"${keybindingStr}"`).length))); this._register(this.defineKeybindingWidget.onShowExistingKeybidings(keybindingStr => this.searchWidget.setValue(`"${keybindingStr}"`))); this.hideOverlayContainer(); } @@ -337,10 +346,6 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.actionsContainer = DOM.append(searchContainer, DOM.$('.keybindings-search-actions-container')); const recordingBadge = this.createRecordingBadge(this.actionsContainer); - const sortByPrecedenceActionKeybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE); - const sortByPrecedenceActionLabel = localize('sortByPrecedeneLabel', "Sort by Precedence"); - this.sortByPrecedenceAction = new Action('keybindings.editor.sortByPrecedence', sortByPrecedenceActionKeybinding ? localize('sortByPrecedeneLabelWithKeybinding', "{0} ({1})", sortByPrecedenceActionLabel, sortByPrecedenceActionKeybinding.getLabel()) : sortByPrecedenceActionLabel, 'codicon-sort-precedence'); - this.sortByPrecedenceAction.checked = false; this._register(this.sortByPrecedenceAction.onDidChange(e => { if (e.checked !== undefined) { this.renderKeybindingsEntries(false); @@ -348,10 +353,6 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.updateSearchOptions(); })); - const recordKeysActionKeybinding = this.keybindingsService.lookupKeybinding(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS); - const recordKeysActionLabel = localize('recordKeysLabel', "Record Keys"); - this.recordKeysAction = new Action(KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, recordKeysActionKeybinding ? localize('recordKeysLabelWithKeybinding', "{0} ({1})", recordKeysActionLabel, recordKeysActionKeybinding.getLabel()) : recordKeysActionLabel, 'codicon-record-keys'); - this.recordKeysAction.checked = false; this._register(this.recordKeysAction.onDidChange(e => { if (e.checked !== undefined) { DOM.toggleClass(recordingBadge, 'disabled', !e.checked); @@ -370,7 +371,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } })); - this.actionBar = this._register(new ActionBar(this.actionsContainer, { + const actionBar = this._register(new ActionBar(this.actionsContainer, { animated: false, actionViewItemProvider: (action: Action) => { if (action.id === this.sortByPrecedenceAction.id) { @@ -383,7 +384,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } })); - this.actionBar.push([this.recordKeysAction, this.sortByPrecedenceAction, clearInputAction], { label: false, icon: true }); + actionBar.push([this.recordKeysAction, this.sortByPrecedenceAction, clearInputAction], { label: false, icon: true }); } private updateSearchOptions(): void { @@ -563,6 +564,9 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } private layoutKeybindingsList(): void { + if (!this.dimension) { + return; + } let width = this.dimension.width - 27; for (const columnItem of this.columnItems) { if (columnItem.width && !columnItem.proportion) { @@ -855,7 +859,7 @@ abstract class Column extends Disposable { class ActionsColumn extends Column { - private actionBar: ActionBar; + private readonly actionBar: ActionBar; readonly element: HTMLElement; constructor( @@ -864,13 +868,8 @@ class ActionsColumn extends Column { @IKeybindingService private keybindingsService: IKeybindingService ) { super(keybindingsEditor); - this.element = this.create(parent); - } - - create(parent: HTMLElement): HTMLElement { - const actionsContainer = DOM.append(parent, $('.column.actions', { id: 'actions_' + ++Column.COUNTER })); - this.actionBar = new ActionBar(actionsContainer, { animated: false }); - return actionsContainer; + this.element = DOM.append(parent, $('.column.actions', { id: 'actions_' + ++Column.COUNTER })); + this.actionBar = new ActionBar(this.element, { animated: false }); } render(keybindingItemEntry: IKeybindingItemEntry): void { @@ -914,7 +913,7 @@ class ActionsColumn extends Column { class CommandColumn extends Column { - private commandColumn: HTMLElement; + private readonly commandColumn: HTMLElement; readonly element: HTMLElement; constructor( @@ -922,12 +921,7 @@ class CommandColumn extends Column { keybindingsEditor: IKeybindingsEditor, ) { super(keybindingsEditor); - this.element = this.create(parent); - } - - private create(parent: HTMLElement): HTMLElement { - this.commandColumn = DOM.append(parent, $('.column.command', { id: 'command_' + ++Column.COUNTER })); - return this.commandColumn; + this.element = this.commandColumn = DOM.append(parent, $('.column.command', { id: 'command_' + ++Column.COUNTER })); } render(keybindingItemEntry: IKeybindingItemEntry): void { @@ -962,7 +956,7 @@ class CommandColumn extends Column { class KeybindingColumn extends Column { - private keybindingLabel: HTMLElement; + private readonly keybindingLabel: HTMLElement; readonly element: HTMLElement; constructor( @@ -970,13 +964,9 @@ class KeybindingColumn extends Column { keybindingsEditor: IKeybindingsEditor, ) { super(keybindingsEditor); - this.element = this.create(parent); - } - private create(parent: HTMLElement): HTMLElement { - const column = DOM.append(parent, $('.column.keybinding', { id: 'keybinding_' + ++Column.COUNTER })); - this.keybindingLabel = DOM.append(column, $('div.keybinding-label')); - return column; + this.element = DOM.append(parent, $('.column.keybinding', { id: 'keybinding_' + ++Column.COUNTER })); + this.keybindingLabel = DOM.append(this.element, $('div.keybinding-label')); } render(keybindingItemEntry: IKeybindingItemEntry): void { @@ -994,7 +984,7 @@ class KeybindingColumn extends Column { class SourceColumn extends Column { - private sourceColumn: HTMLElement; + private readonly sourceColumn: HTMLElement; readonly element: HTMLElement; constructor( @@ -1002,12 +992,7 @@ class SourceColumn extends Column { keybindingsEditor: IKeybindingsEditor, ) { super(keybindingsEditor); - this.element = this.create(parent); - } - - create(parent: HTMLElement): HTMLElement { - this.sourceColumn = DOM.append(parent, $('.column.source', { id: 'source_' + ++Column.COUNTER })); - return this.sourceColumn; + this.element = this.sourceColumn = DOM.append(parent, $('.column.source', { id: 'source_' + ++Column.COUNTER })); } render(keybindingItemEntry: IKeybindingItemEntry): void { @@ -1024,8 +1009,8 @@ class SourceColumn extends Column { class WhenColumn extends Column { readonly element: HTMLElement; - private whenLabel: HTMLElement; - private whenInput: InputBox; + private readonly whenLabel: HTMLElement; + private readonly whenInput: InputBox; private readonly renderDisposables = this._register(new DisposableStore()); private _onDidAccept: Emitter = this._register(new Emitter()); @@ -1041,14 +1026,11 @@ class WhenColumn extends Column { @IThemeService private readonly themeService: IThemeService ) { super(keybindingsEditor); - this.element = this.create(parent); - } - private create(parent: HTMLElement): HTMLElement { - const column = DOM.append(parent, $('.column.when', { id: 'when_' + ++Column.COUNTER })); + this.element = DOM.append(parent, $('.column.when', { id: 'when_' + ++Column.COUNTER })); - this.whenLabel = DOM.append(column, $('div.when-label')); - this.whenInput = new InputBox(column, this.contextViewService, { + this.whenLabel = DOM.append(this.element, $('div.when-label')); + this.whenInput = new InputBox(this.element, this.contextViewService, { validationOptions: { validation: (value) => { try { @@ -1068,8 +1050,6 @@ class WhenColumn extends Column { this._register(attachInputBoxStyler(this.whenInput, this.themeService)); this._register(DOM.addStandardDisposableListener(this.whenInput.inputElement, DOM.EventType.KEY_DOWN, e => this.onInputKeyDown(e))); this._register(DOM.addDisposableListener(this.whenInput.inputElement, DOM.EventType.BLUR, () => this.cancelEditing())); - - return column; } private onInputKeyDown(e: IKeyboardEvent): void { From f54691d1b329a1d48ff95af7ef37056d48dbe07f Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 11 Nov 2019 15:43:47 +0100 Subject: [PATCH 31/49] debug: enabling breakpoints should set them to activated fixes #83323 --- src/vs/workbench/contrib/debug/common/debugModel.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 2dc46ce56f9..0e647c09b23 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -1093,6 +1093,9 @@ export class DebugModel implements IDebugModel { } element.enabled = enable; + if (enable) { + this.breakpointsActivated = true; + } this._onDidChangeBreakpoints.fire({ changed: changed }); } @@ -1119,6 +1122,9 @@ export class DebugModel implements IDebugModel { } dbp.enabled = enable; }); + if (enable) { + this.breakpointsActivated = true; + } this._onDidChangeBreakpoints.fire({ changed: changed }); } From f9285ac2b58a0bf385a229542f8b025e6baf8b07 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Mon, 11 Nov 2019 12:37:22 +0100 Subject: [PATCH 32/49] use LogService in extHostStoragePaths and extHostRequireInterceptor, #84283 --- .../workbench/api/common/extHostRequireInterceptor.ts | 11 +++++++---- src/vs/workbench/api/node/extHostStoragePaths.ts | 8 ++++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/api/common/extHostRequireInterceptor.ts b/src/vs/workbench/api/common/extHostRequireInterceptor.ts index 158239df616..b6f94048fbd 100644 --- a/src/vs/workbench/api/common/extHostRequireInterceptor.ts +++ b/src/vs/workbench/api/common/extHostRequireInterceptor.ts @@ -18,6 +18,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { platform } from 'vs/base/common/process'; +import { ILogService } from 'vs/platform/log/common/log'; interface LoadFunction { @@ -41,7 +42,8 @@ export abstract class RequireInterceptor { @IInstantiationService private readonly _instaService: IInstantiationService, @IExtHostConfiguration private readonly _extHostConfiguration: IExtHostConfiguration, @IExtHostExtensionService private readonly _extHostExtensionService: IExtHostExtensionService, - @IExtHostInitDataService private readonly _initData: IExtHostInitDataService + @IExtHostInitDataService private readonly _initData: IExtHostInitDataService, + @ILogService private readonly _logService: ILogService, ) { this._factories = new Map(); this._alternatives = []; @@ -54,7 +56,7 @@ export abstract class RequireInterceptor { const configProvider = await this._extHostConfiguration.getConfigProvider(); const extensionPaths = await this._extHostExtensionService.getExtensionPathIndex(); - this.register(new VSCodeNodeModuleFactory(this._apiFactory, extensionPaths, this._extensionRegistry, configProvider)); + this.register(new VSCodeNodeModuleFactory(this._apiFactory, extensionPaths, this._extensionRegistry, configProvider, this._logService)); this.register(this._instaService.createInstance(KeytarNodeModuleFactory)); if (this._initData.remote.isRemote) { this.register(this._instaService.createInstance(OpenNodeModuleFactory, extensionPaths, this._initData.environment.appUriScheme)); @@ -91,7 +93,8 @@ class VSCodeNodeModuleFactory implements INodeModuleFactory { private readonly _apiFactory: IExtensionApiFactory, private readonly _extensionPaths: TernarySearchTree, private readonly _extensionRegistry: ExtensionDescriptionRegistry, - private readonly _configProvider: ExtHostConfigProvider + private readonly _configProvider: ExtHostConfigProvider, + private readonly _logService: ILogService, ) { } @@ -112,7 +115,7 @@ class VSCodeNodeModuleFactory implements INodeModuleFactory { if (!this._defaultApiImpl) { let extensionPathsPretty = ''; this._extensionPaths.forEach((value, index) => extensionPathsPretty += `\t${index} -> ${value.identifier.value}\n`); - console.warn(`Could not identify extension for 'vscode' require call from ${parent.fsPath}. These are the extension path mappings: \n${extensionPathsPretty}`); + this._logService.warn(`Could not identify extension for 'vscode' require call from ${parent.fsPath}. These are the extension path mappings: \n${extensionPathsPretty}`); this._defaultApiImpl = this._apiFactory(nullExtensionDescription, this._extensionRegistry, this._configProvider); } return this._defaultApiImpl; diff --git a/src/vs/workbench/api/node/extHostStoragePaths.ts b/src/vs/workbench/api/node/extHostStoragePaths.ts index 5a301ad857c..afdd6bf3984 100644 --- a/src/vs/workbench/api/node/extHostStoragePaths.ts +++ b/src/vs/workbench/api/node/extHostStoragePaths.ts @@ -11,6 +11,7 @@ import { IExtensionDescription } from 'vs/platform/extensions/common/extensions' import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { ILogService } from 'vs/platform/log/common/log'; export class ExtensionStoragePaths implements IExtensionStoragePaths { @@ -22,7 +23,10 @@ export class ExtensionStoragePaths implements IExtensionStoragePaths { readonly whenReady: Promise; private _value?: string; - constructor(@IExtHostInitDataService initData: IExtHostInitDataService) { + constructor( + @IExtHostInitDataService initData: IExtHostInitDataService, + @ILogService private readonly _logService: ILogService, + ) { this._workspace = withNullAsUndefined(initData.workspace); this._environment = initData.environment; this.whenReady = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value); @@ -69,7 +73,7 @@ export class ExtensionStoragePaths implements IExtensionStoragePaths { return storagePath; } catch (e) { - console.error(e); + this._logService.error(e); return undefined; } } From 100318a63e2af38b5a442362d25742d732fc0d94 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Mon, 11 Nov 2019 16:04:09 +0100 Subject: [PATCH 33/49] styling in settings and themes --- .../common/tokenClassificationRegistry.ts | 170 +++++++++++++----- .../browser/abstractTextMateService.ts | 4 +- .../themes/browser/workbenchThemeService.ts | 24 ++- .../services/themes/common/colorThemeData.ts | 128 +++++++++---- .../themes/common/themeCompatibility.ts | 8 +- .../themes/common/workbenchThemeService.ts | 17 +- .../tokenStyleResolving.test.ts | 52 +++--- 7 files changed, 281 insertions(+), 122 deletions(-) diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 7817b7a5778..2ec489ae46f 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -7,6 +7,10 @@ import * as platform from 'vs/platform/registry/common/platform'; import { Color } from 'vs/base/common/color'; import { ITheme } from 'vs/platform/theme/common/themeService'; import * as nls from 'vs/nls'; +import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; // ------ API types @@ -89,6 +93,8 @@ export const Extensions = { export interface ITokenClassificationRegistry { + readonly onDidChangeSchema: Event; + /** * Register a token type to the registry. * @param id The TokenType id as used in theme description files @@ -106,7 +112,7 @@ export interface ITokenClassificationRegistry { getTokenClassificationFromString(str: TokenClassificationString): TokenClassification | undefined; getTokenClassification(type: string, modifiers: string[]): TokenClassification | undefined; - getTokenStylingRule(classification: TokenClassification | string | undefined, value: TokenStyle): TokenStylingRule | undefined; + getTokenStylingRule(classification: TokenClassification, value: TokenStyle): TokenStylingRule; /** * Register a TokenStyle default to the registry. @@ -138,13 +144,19 @@ export interface ITokenClassificationRegistry { /** * Resolves a token classification against the given rules and default rules from the registry. */ - resolveTokenStyle(classification: TokenClassification, themingRules: TokenStylingRule[], useDefault: boolean, theme: ITheme): TokenStyle | undefined; + resolveTokenStyle(classification: TokenClassification, themingRules: TokenStylingRule[] | undefined, customThemingRules: TokenStylingRule[], theme: ITheme): TokenStyle | undefined; + + /** + * JSON schema for an object to assign styling to token classifications + */ + getTokenStylingSchema(): IJSONSchema; } - - class TokenClassificationRegistry implements ITokenClassificationRegistry { + private readonly _onDidChangeSchema = new Emitter(); + readonly onDidChangeSchema: Event = this._onDidChangeSchema.event; + private currentTypeNumber = 0; private currentModifierBit = 1; @@ -153,6 +165,38 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { private tokenStylingDefaultRules: TokenStylingDefaultRule[] = []; + private tokenStylingSchema: IJSONSchema & { properties: IJSONSchemaMap } = { + type: 'object', + properties: {}, + definitions: { + style: { + type: 'object', + description: nls.localize('schema.token.settings', 'Colors and styles for the token.'), + properties: { + foreground: { + type: 'string', + description: nls.localize('schema.token.foreground', 'Foreground color for the token.'), + format: 'color-hex', + default: '#ff0000' + }, + background: { + type: 'string', + deprecationMessage: nls.localize('schema.token.background.warning', 'Token background colors are currently not supported.') + }, + fontStyle: { + type: 'string', + description: nls.localize('schema.token.fontStyle', 'Font style of the rule: \'italic\', \'bold\' or \'underline\', \'-italic\', \'-bold\' or \'-underline\'or a combination. The empty string unsets inherited settings.'), + pattern: '^(\\s*(-?italic|-?bold|-?underline))*\\s*$', + patternErrorMessage: nls.localize('schema.fontStyle.error', 'Font style must be \'italic\', \'bold\' or \'underline\' to set a style or \'-italic\', \'-bold\' or \'-underline\' to unset or a combination. The empty string unsets all styles.'), + defaultSnippets: [{ label: nls.localize('schema.token.fontStyle.none', 'None (clear inherited style)'), bodyText: '""' }, { body: 'italic' }, { body: 'bold' }, { body: 'underline' }, { body: '-italic' }, { body: '-bold' }, { body: '-underline' }, { body: 'italic bold' }, { body: 'italic underline' }, { body: 'bold underline' }, { body: 'italic bold underline' }] + } + }, + additionalProperties: false, + defaultSnippets: [{ body: { foreground: '${1:#FF0000}', fontStyle: '${2:bold}' } }] + } + } + }; + constructor() { this.tokenTypeById = {}; this.tokenModifierById = {}; @@ -164,6 +208,8 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { const num = this.currentTypeNumber++; let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, description, deprecationMessage }; this.tokenTypeById[id] = tokenStyleContribution; + + this.tokenStylingSchema.properties[id] = getStylingSchemeEntry(description, deprecationMessage); } public registerTokenModifier(id: string, description: string, deprecationMessage?: string): void { @@ -171,6 +217,8 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { this.currentModifierBit = this.currentModifierBit * 2; let tokenStyleContribution: TokenTypeOrModifierContribution = { num, id, description, deprecationMessage }; this.tokenModifierById[id] = tokenStyleContribution; + + this.tokenStylingSchema.properties[`*.${id}`] = getStylingSchemeEntry(description, deprecationMessage); } public getTokenClassification(type: string, modifiers: string[]): TokenClassification | undefined { @@ -197,14 +245,8 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { return undefined; } - public getTokenStylingRule(classification: TokenClassification | string | undefined, value: TokenStyle): TokenStylingRule | undefined { - if (typeof classification === 'string') { - classification = this.getTokenClassificationFromString(classification); - } - if (classification) { - return { classification, matchScore: getTokenStylingScore(classification), value }; - } - return undefined; + public getTokenStylingRule(classification: TokenClassification, value: TokenStyle): TokenStylingRule { + return { classification, matchScore: getTokenStylingScore(classification), value }; } public registerTokenStyleDefault(classification: TokenClassification, defaults: TokenStyleDefaults): void { @@ -213,10 +255,12 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { public deregisterTokenType(id: string): void { delete this.tokenTypeById[id]; + delete this.tokenStylingSchema.properties[id]; } public deregisterTokenModifier(id: string): void { delete this.tokenModifierById[id]; + delete this.tokenStylingSchema.properties[`*.${id}`]; } public getTokenTypes(): TokenTypeOrModifierContribution[] { @@ -227,7 +271,7 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { return Object.keys(this.tokenModifierById).map(id => this.tokenModifierById[id]); } - public resolveTokenStyle(classification: TokenClassification, themingRules: TokenStylingRule[], useDefault: boolean, theme: ITheme): TokenStyle | undefined { + public resolveTokenStyle(classification: TokenClassification, themingRules: TokenStylingRule[] | undefined, customThemingRules: TokenStylingRule[], theme: ITheme): TokenStyle | undefined { let result: any = { foreground: undefined, bold: undefined, @@ -257,7 +301,7 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { } } } - if (useDefault) { + if (themingRules === undefined) { for (const rule of this.tokenStylingDefaultRules) { const matchScore = match(rule, classification); if (matchScore >= 0) { @@ -270,8 +314,15 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { } } } + } else { + for (const rule of themingRules) { + const matchScore = match(rule, classification); + if (matchScore >= 0) { + _processStyle(matchScore, rule.value); + } + } } - for (const rule of themingRules) { + for (const rule of customThemingRules) { const matchScore = match(rule, classification); if (matchScore >= 0) { _processStyle(matchScore, rule.value); @@ -297,6 +348,10 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { return undefined; } + public getTokenStylingSchema(): IJSONSchema { + return this.tokenStylingSchema; + } + public toString() { let sorter = (a: string, b: string) => { @@ -348,40 +403,40 @@ export function getTokenClassificationRegistry(): ITokenClassificationRegistry { return tokenClassificationRegistry; } -export const comments = registerTokenType('comments', nls.localize('comments', "Token style for comments."), [['comment']]); -export const strings = registerTokenType('strings', nls.localize('strings', "Token style for strings."), [['string']]); -export const keywords = registerTokenType('keywords', nls.localize('keywords', "Token style for keywords."), [['keyword.control']]); -export const numbers = registerTokenType('numbers', nls.localize('numbers', "Token style for numbers."), [['constant.numeric']]); -export const regexp = registerTokenType('regexp', nls.localize('regexp', "Token style for regular expressions."), [['constant.regexp']]); -export const operators = registerTokenType('operators', nls.localize('operator', "Token style for operators."), [['keyword.operator']]); +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']]); -export const namespaces = registerTokenType('namespaces', nls.localize('namespace', "Token style for namespaces."), [['entity.name.namespace']]); +export const namespaces = registerTokenType('namespaces', nls.localize('namespace', "Style for namespaces."), [['entity.name.namespace']]); -export const types = registerTokenType('types', nls.localize('types', "Token style for types."), [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']]); -export const structs = registerTokenType('structs', nls.localize('struct', "Token style for struct."), [['storage.type.struct']], types); -export const classes = registerTokenType('classes', nls.localize('class', "Token style for classes."), [['ntity.name.class']], types); -export const interfaces = registerTokenType('interfaces', nls.localize('interface', "Token style for interfaces."), undefined, types); -export const enums = registerTokenType('enums', nls.localize('enum', "Token style for enums."), undefined, types); -export const parameterTypes = registerTokenType('parameterTypes', nls.localize('parameterType', "Token style for parameterTypes."), undefined, types); +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); -export const functions = registerTokenType('functions', nls.localize('functions', "Token style for functions."), [['entity.name.function'], ['support.function']]); -export const macros = registerTokenType('macros', nls.localize('macro', "Token style for macros."), undefined, functions); +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); -export const variables = registerTokenType('variables', nls.localize('variables', "Token style for variables."), [['variable'], ['entity.name.variable']]); -export const constants = registerTokenType('constants', nls.localize('constants', "Token style for constants."), undefined, variables); -export const parameters = registerTokenType('parameters', nls.localize('parameters', "Token style for parameters."), undefined, variables); -export const property = registerTokenType('properties', nls.localize('properties', "Token style for properties."), undefined, variables); +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); -export const labels = registerTokenType('labels', nls.localize('labels', "Token style for labels."), undefined); +export const labels = registerTokenType('labels', nls.localize('labels', "Style for labels. "), undefined); -export const m_declaration = registerTokenModifier('declaration', nls.localize('declaration', "Token modifier for declarations."), undefined); -export const m_documentation = registerTokenModifier('documentation', nls.localize('documentation', "Token modifier for documentation."), undefined); -export const m_member = registerTokenModifier('member', nls.localize('member', "Token modifier for member."), undefined); -export const m_static = registerTokenModifier('static', nls.localize('static', "Token modifier for statics."), undefined); -export const m_abstract = registerTokenModifier('abstract', nls.localize('abstract', "Token modifier for abstracts."), undefined); -export const m_deprecated = registerTokenModifier('deprecated', nls.localize('deprecated', "Token modifier for deprecated."), undefined); -export const m_modification = registerTokenModifier('modification', nls.localize('modification', "Token modifier for modification."), undefined); -export const m_async = registerTokenModifier('async', nls.localize('async', "Token modifier for 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/ @@ -392,3 +447,32 @@ function bitCount(u: number) { function getTokenStylingScore(classification: TokenClassification) { return bitCount(classification.modifiers) + ((classification.type !== TOKEN_TYPE_WILDCARD_NUM) ? 1 : 0); } + +function getStylingSchemeEntry(description: string, deprecationMessage?: string): IJSONSchema { + return { + description, + deprecationMessage, + defaultSnippets: [{ body: '${1:#ff0000}' }], + anyOf: [ + { + type: 'string', + format: 'color-hex' + }, + { + $ref: '#definitions/style' + } + ] + }; +} + +export const tokenStylingSchemaId = 'vscode://schemas/token-styling'; + +let schemaRegistry = platform.Registry.as(JSONExtensions.JSONContribution); +schemaRegistry.registerSchema(tokenStylingSchemaId, tokenClassificationRegistry.getTokenStylingSchema()); + +const delayer = new RunOnceScheduler(() => schemaRegistry.notifySchemaChanged(tokenStylingSchemaId), 200); +tokenClassificationRegistry.onDidChangeSchema(() => { + if (!delayer.isScheduled()) { + delayer.schedule(); + } +}); diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index cb553b6fcc8..dc9222e4b02 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -22,7 +22,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ITMSyntaxExtensionPoint, grammarsExtPoint } from 'vs/workbench/services/textMate/common/TMGrammars'; import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; -import { ITokenColorizationRule, IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ITextMateThemingRule, IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IGrammar, StackElement, IOnigLib, IRawTheme } from 'vscode-textmate'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -257,7 +257,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex TokenizationRegistry.setColorMap(colorMap); } - private static equalsTokenRules(a: ITokenColorizationRule[] | null, b: ITokenColorizationRule[] | null): boolean { + private static equalsTokenRules(a: ITextMateThemingRule[] | null, b: ITextMateThemingRule[] | null): boolean { if (!b || !a || b.length !== a.length) { return false; } diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 85b09406b2c..91e4c86fe53 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IColorTheme, ITokenColorCustomizations, IFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, COLOR_THEME_SETTING, ICON_THEME_SETTING, CUSTOM_WORKBENCH_COLORS_SETTING, CUSTOM_EDITOR_COLORS_SETTING, DETECT_HC_SETTING, HC_THEME_ID, IColorCustomizations, CUSTOM_EDITOR_TOKENSTYLES_SETTING, IExperimentalTokenStyleCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -29,6 +29,7 @@ import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { textmateColorsSchemaId, registerColorThemeSchemas, textmateColorSettingsSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; +import { tokenStylingSchemaId } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; @@ -92,6 +93,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return this.configurationService.getValue(CUSTOM_EDITOR_COLORS_SETTING) || {}; } + private get tokenStylesCustomizations(): IExperimentalTokenStyleCustomizations { + return this.configurationService.getValue(CUSTOM_EDITOR_TOKENSTYLES_SETTING) || {}; + } + constructor( @IExtensionService extensionService: IExtensionService, @IStorageService private readonly storageService: IStorageService, @@ -126,6 +131,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } themeData.setCustomColors(this.colorCustomizations); themeData.setCustomTokenColors(this.tokenColorCustomizations); + themeData.setCustomTokenStyleRules(this.tokenStylesCustomizations); this.updateDynamicCSSRules(themeData); this.applyTheme(themeData, undefined, true); @@ -154,18 +160,22 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { const themeSpecificWorkbenchColors: IJSONSchema = { properties: {} }; const themeSpecificTokenColors: IJSONSchema = { properties: {} }; + const themeSpecificTokenStyling: IJSONSchema = { properties: {} }; const workbenchColors = { $ref: workbenchColorsSchemaId, additionalProperties: false }; const tokenColors = { properties: tokenColorSchema.properties, additionalProperties: false }; + const tokenStyling = { $ref: tokenStylingSchemaId, additionalProperties: false }; for (let t of event.themes) { // add theme specific color customization ("[Abyss]":{ ... }) const themeId = `[${t.settingsId}]`; themeSpecificWorkbenchColors.properties![themeId] = workbenchColors; themeSpecificTokenColors.properties![themeId] = tokenColors; + themeSpecificTokenStyling.properties![themeId] = tokenStyling; } colorCustomizationsSchema.allOf![1] = themeSpecificWorkbenchColors; tokenColorCustomizationSchema.allOf![1] = themeSpecificTokenColors; + experimentalTokenStylingCustomizationSchema.allOf![1] = themeSpecificTokenStyling; configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration, tokenColorCustomizationConfiguration); @@ -308,6 +318,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.currentColorTheme.setCustomTokenColors(this.tokenColorCustomizations); hasColorChanges = true; } + if (e.affectsConfiguration(CUSTOM_EDITOR_TOKENSTYLES_SETTING)) { + this.currentColorTheme.setCustomTokenStyleRules(this.tokenStylesCustomizations); + hasColorChanges = true; + } if (hasColorChanges) { this.updateDynamicCSSRules(this.currentColorTheme); this.onColorThemeChange.fire(this.currentColorTheme); @@ -698,12 +712,18 @@ const tokenColorCustomizationSchema: IConfigurationPropertySchema = { default: {}, allOf: [tokenColorSchema] }; +const experimentalTokenStylingCustomizationSchema: IConfigurationPropertySchema = { + description: nls.localize('editorColorsTokenStyles', "Overrides token color and styles from the currently selected color theme."), + default: {}, + allOf: [{ $ref: tokenStylingSchemaId }] +}; const tokenColorCustomizationConfiguration: IConfigurationNode = { id: 'editor', order: 7.2, type: 'object', properties: { - [CUSTOM_EDITOR_COLORS_SETTING]: tokenColorCustomizationSchema + [CUSTOM_EDITOR_COLORS_SETTING]: tokenColorCustomizationSchema, + [CUSTOM_EDITOR_TOKENSTYLES_SETTING]: experimentalTokenStylingCustomizationSchema } }; configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration); diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index e417676fc2e..4a1b760b4e2 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -6,7 +6,7 @@ import { basename } from 'vs/base/common/path'; import * as Json from 'vs/base/common/json'; import { Color } from 'vs/base/common/color'; -import { ExtensionData, ITokenColorCustomizations, ITokenColorizationRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ExtensionData, ITokenColorCustomizations, ITextMateThemingRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME, IColorCustomizations, IExperimentalTokenStyleCustomizations, ITokenColorizationSetting } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { convertSettings } from 'vs/workbench/services/themes/common/themeCompatibility'; import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; @@ -49,12 +49,13 @@ export class ColorThemeData implements IColorTheme { watch?: boolean; extensionData?: ExtensionData; - private themeTokenColors: ITokenColorizationRule[] = []; - private customTokenColors: ITokenColorizationRule[] = []; + private themeTokenColors: ITextMateThemingRule[] = []; + private customTokenColors: ITextMateThemingRule[] = []; private colorMap: IColorMap = {}; private customColorMap: IColorMap = {}; - private tokenStylingRules: TokenStylingRule[] = []; + private tokenStylingRules: TokenStylingRule[] | undefined = undefined; + private customTokenStylingRules: TokenStylingRule[] = []; private themeTokenScopeMatchers: Matcher[] | undefined; private customTokenScopeMatchers: Matcher[] | undefined; @@ -66,8 +67,8 @@ export class ColorThemeData implements IColorTheme { this.isLoaded = false; } - get tokenColors(): ITokenColorizationRule[] { - const result: ITokenColorizationRule[] = []; + get tokenColors(): ITextMateThemingRule[] { + const result: ITextMateThemingRule[] = []; // the default rule (scope empty) is always the first rule. Ignore all other default rules. const foreground = this.getColor(editorForeground) || this.getDefault(editorForeground)!; @@ -81,7 +82,7 @@ export class ColorThemeData implements IColorTheme { let hasDefaultTokens = false; - function addRule(rule: ITokenColorizationRule) { + function addRule(rule: ITextMateThemingRule) { if (rule.scope && rule.settings) { if (rule.scope === 'token.info-token') { hasDefaultTokens = true; @@ -115,7 +116,7 @@ export class ColorThemeData implements IColorTheme { public getTokenStyle(tokenClassification: TokenClassification, useDefault?: boolean): TokenStyle | undefined { // todo: cache results - return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, this.tokenStylingRules, useDefault !== false, this); + return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, this.tokenStylingRules, this.customTokenStylingRules, this); } public getDefault(colorId: ColorIdentifier): Color | undefined { @@ -123,7 +124,7 @@ export class ColorThemeData implements IColorTheme { } public getDefaultTokenStyle(tokenClassification: TokenClassification): TokenStyle | undefined { - return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, [], true, this); + return tokenClassificationRegistry.resolveTokenStyle(tokenClassification, undefined, [], this); } public resolveScopes(scopes: ProbeScope[]): TokenStyle | undefined { @@ -136,12 +137,12 @@ export class ColorThemeData implements IColorTheme { } for (let scope of scopes) { - let foreground: string | null = null; - let fontStyle: string | null = null; + let foreground: string | undefined = undefined; + let fontStyle: string | undefined = undefined; let foregroundScore = -1; let fontStyleScore = -1; - function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITokenColorizationRule[]) { + function findTokenStyleForScopeInScopes(scopeMatchers: Matcher[], tokenColors: ITextMateThemingRule[]) { for (let i = 0; i < scopeMatchers.length; i++) { const score = scopeMatchers[i](scope); if (score >= 0) { @@ -157,7 +158,7 @@ export class ColorThemeData implements IColorTheme { } findTokenStyleForScopeInScopes(this.themeTokenScopeMatchers, this.themeTokenColors); findTokenStyleForScopeInScopes(this.customTokenScopeMatchers, this.customTokenColors); - if (foreground !== null || fontStyle !== null) { + if (foreground !== undefined || fontStyle !== undefined) { return getTokenStyle(foreground, fontStyle); } } @@ -200,8 +201,14 @@ export class ColorThemeData implements IColorTheme { } } - public setTokenStyleRules(tokenStylingRules: TokenStylingRule[]) { - this.tokenStylingRules = tokenStylingRules; + public setCustomTokenStyleRules(tokenStylingRules: IExperimentalTokenStyleCustomizations) { + this.tokenStylingRules = []; + readCustomTokenStyleRules(tokenStylingRules, this.tokenStylingRules); + + const themeSpecificColors = tokenStylingRules[`[${this.settingsId}]`] as IExperimentalTokenStyleCustomizations; + if (types.isObject(themeSpecificColors)) { + readCustomTokenStyleRules(themeSpecificColors, this.tokenStylingRules); + } } private addCustomTokenColors(customTokenColors: ITokenColorCustomizations) { @@ -243,9 +250,17 @@ export class ColorThemeData implements IColorTheme { } this.themeTokenColors = []; this.themeTokenScopeMatchers = undefined; - this.colorMap = {}; - return _loadColorTheme(extensionResourceLoaderService, this.location, this.themeTokenColors, this.colorMap).then(_ => { + + const result = { + colors: {}, + textMateRules: [], + stylingRules: undefined + }; + return _loadColorTheme(extensionResourceLoaderService, this.location, result).then(_ => { this.isLoaded = true; + this.tokenStylingRules = result.stylingRules; + this.colorMap = result.colors; + this.themeTokenColors = result.textMateRules; }); } @@ -358,7 +373,7 @@ function toCSSSelector(extensionId: string, path: string) { return str; } -function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): Promise { +function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap, stylingRules: TokenStylingRule[] | undefined }): Promise { if (resources.extname(themeLocation) === '.json') { return extensionResourceLoaderService.readExtensionResource(themeLocation).then(content => { let errors: Json.ParseError[] = []; @@ -370,11 +385,11 @@ function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoade } let includeCompletes: Promise = Promise.resolve(null); if (contentValue.include) { - includeCompletes = _loadColorTheme(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), contentValue.include), resultRules, resultColors); + includeCompletes = _loadColorTheme(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), contentValue.include), result); } return includeCompletes.then(_ => { if (Array.isArray(contentValue.settings)) { - convertSettings(contentValue.settings, resultRules, resultColors); + convertSettings(contentValue.settings, result); return null; } let colors = contentValue.colors; @@ -386,38 +401,42 @@ function _loadColorTheme(extensionResourceLoaderService: IExtensionResourceLoade for (let colorId in colors) { let colorHex = colors[colorId]; if (typeof colorHex === 'string') { // ignore colors tht are null - resultColors[colorId] = Color.fromHex(colors[colorId]); + result.colors[colorId] = Color.fromHex(colors[colorId]); } } } let tokenColors = contentValue.tokenColors; if (tokenColors) { if (Array.isArray(tokenColors)) { - resultRules.push(...tokenColors); + result.textMateRules.push(...tokenColors); return null; } else if (typeof tokenColors === 'string') { - return _loadSyntaxTokens(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), tokenColors), resultRules, {}); + return _loadSyntaxTokens(extensionResourceLoaderService, resources.joinPath(resources.dirname(themeLocation), tokenColors), result); } else { return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.tokenColors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'tokenColors' should be either an array specifying colors or a path to a TextMate theme file", themeLocation.toString()))); } } + let tokenStylingRules = contentValue.tokenStylingRules; + if (tokenStylingRules && typeof tokenStylingRules === 'object') { + result.stylingRules = readCustomTokenStyleRules(tokenStylingRules, result.stylingRules); + } return null; }); }); } else { - return _loadSyntaxTokens(extensionResourceLoaderService, themeLocation, resultRules, resultColors); + return _loadSyntaxTokens(extensionResourceLoaderService, themeLocation, result); } } -function _loadSyntaxTokens(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): Promise { +function _loadSyntaxTokens(extensionResourceLoaderService: IExtensionResourceLoaderService, themeLocation: URI, result: { textMateRules: ITextMateThemingRule[], colors: IColorMap }): Promise { return extensionResourceLoaderService.readExtensionResource(themeLocation).then(content => { try { let contentValue = parsePList(content); - let settings: ITokenColorizationRule[] = contentValue.settings; + let settings: ITextMateThemingRule[] = contentValue.settings; if (!Array.isArray(settings)) { return Promise.reject(new Error(nls.localize('error.plist.invalidformat', "Problem parsing tmTheme file: {0}. 'settings' is not array."))); } - convertSettings(settings, resultRules, resultColors); + convertSettings(settings, result); return Promise.resolve(null); } catch (e) { return Promise.reject(new Error(nls.localize('error.cannotparse', "Problems parsing tmTheme file: {0}", e.message))); @@ -427,7 +446,7 @@ function _loadSyntaxTokens(extensionResourceLoaderService: IExtensionResourceLoa }); } -let defaultThemeColors: { [baseTheme: string]: ITokenColorizationRule[] } = { +let defaultThemeColors: { [baseTheme: string]: ITextMateThemingRule[] } = { 'light': [ { scope: 'token.info-token', settings: { foreground: '#316bcd' } }, { scope: 'token.warn-token', settings: { foreground: '#cd9731' } }, @@ -489,7 +508,7 @@ function scopesAreMatching(thisScopeName: string, scopeName: string): boolean { return thisScopeName.length > len && thisScopeName.substr(0, len) === scopeName && thisScopeName[len] === '.'; } -function getScopeMatcher(rule: ITokenColorizationRule): Matcher { +function getScopeMatcher(rule: ITextMateThemingRule): Matcher { const ruleScope = rule.scope; if (!ruleScope || !rule.settings) { return noMatch; @@ -515,17 +534,56 @@ function getScopeMatcher(rule: ITokenColorizationRule): Matcher { }; } -function getTokenStyle(foreground: string | null, fontStyle: string | null): TokenStyle | undefined { +function getTokenStyle(foreground: string | undefined, fontStyle: string | undefined): TokenStyle { let foregroundColor = undefined; - if (foreground !== null) { + if (foreground !== undefined) { foregroundColor = Color.fromHex(foreground); } let bold, underline, italic; - if (fontStyle !== null) { - bold = fontStyle.indexOf('bold') !== -1; - underline = fontStyle.indexOf('underline') !== -1; - italic = fontStyle.indexOf('italic') !== -1; + if (fontStyle !== undefined) { + fontStyle = fontStyle.trim(); + if (fontStyle.length === 0) { + bold = italic = underline = false; + } else { + const expression = /-?italic|-?bold|-?underline/g; + let match; + while ((match = expression.exec(fontStyle))) { + switch (match[0]) { + case 'bold': bold = true; break; + case '-bold': bold = false; break; + case 'italic': italic = true; break; + case '-italic': italic = false; break; + case 'underline': underline = true; break; + case '-underline': underline = false; break; + } + } + } } return new TokenStyle(foregroundColor, bold, underline, italic); } + +function readCustomTokenStyleRules(tokenStylingRuleSection: IExperimentalTokenStyleCustomizations, result: TokenStylingRule[] = []) { + for (let key in tokenStylingRuleSection) { + if (key[0] !== '[') { + const classification = tokenClassificationRegistry.getTokenClassificationFromString(key); + if (classification) { + const settings = tokenStylingRuleSection[key]; + let style: TokenStyle | undefined; + if (typeof settings === 'string') { + style = getTokenStyle(settings, undefined); + } else if (isTokenColorizationSetting(settings)) { + style = getTokenStyle(settings.foreground, settings.fontStyle); + } + if (style) { + result.push(tokenClassificationRegistry.getTokenStylingRule(classification, style)); + } + } + } + } + return result; +} + +function isTokenColorizationSetting(style: any): style is ITokenColorizationSetting { + return style && (style.foreground || style.fontStyle); +} diff --git a/src/vs/workbench/services/themes/common/themeCompatibility.ts b/src/vs/workbench/services/themes/common/themeCompatibility.ts index 6518ff4a4ac..7af65c955ad 100644 --- a/src/vs/workbench/services/themes/common/themeCompatibility.ts +++ b/src/vs/workbench/services/themes/common/themeCompatibility.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITokenColorizationRule, IColorMap } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ITextMateThemingRule, IColorMap } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { Color } from 'vs/base/common/color'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; @@ -18,9 +18,9 @@ function addSettingMapping(settingId: string, colorId: string) { colorIds.push(colorId); } -export function convertSettings(oldSettings: ITokenColorizationRule[], resultRules: ITokenColorizationRule[], resultColors: IColorMap): void { +export function convertSettings(oldSettings: ITextMateThemingRule[], result: { textMateRules: ITextMateThemingRule[], colors: IColorMap }): void { for (let rule of oldSettings) { - resultRules.push(rule); + result.textMateRules.push(rule); if (!rule.scope) { let settings = rule.settings; if (!settings) { @@ -34,7 +34,7 @@ export function convertSettings(oldSettings: ITokenColorizationRule[], resultRul if (typeof colorHex === 'string') { let color = Color.fromHex(colorHex); for (let colorId of mappings) { - resultColors[colorId] = color; + result.colors[colorId] = color; } } } diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index 8684b54a804..9937ca11be9 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -22,6 +22,7 @@ export const DETECT_HC_SETTING = 'window.autoDetectHighContrast'; export const ICON_THEME_SETTING = 'workbench.iconTheme'; export const CUSTOM_WORKBENCH_COLORS_SETTING = 'workbench.colorCustomizations'; export const CUSTOM_EDITOR_COLORS_SETTING = 'editor.tokenColorCustomizations'; +export const CUSTOM_EDITOR_TOKENSTYLES_SETTING = 'editor.tokenColorCustomizationsExperimental'; export interface IColorTheme extends ITheme { readonly id: string; @@ -30,7 +31,7 @@ export interface IColorTheme extends ITheme { readonly extensionData?: ExtensionData; readonly description?: string; readonly isLoaded: boolean; - readonly tokenColors: ITokenColorizationRule[]; + readonly tokenColors: ITextMateThemingRule[]; } export interface IColorMap { @@ -69,7 +70,7 @@ export interface IColorCustomizations { } export interface ITokenColorCustomizations { - [groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITokenColorizationRule[]; + [groupIdOrThemeSettingsId: string]: string | ITokenColorizationSetting | ITokenColorCustomizations | undefined | ITextMateThemingRule[]; comments?: string | ITokenColorizationSetting; strings?: string | ITokenColorizationSetting; numbers?: string | ITokenColorizationSetting; @@ -77,10 +78,14 @@ export interface ITokenColorCustomizations { types?: string | ITokenColorizationSetting; functions?: string | ITokenColorizationSetting; variables?: string | ITokenColorizationSetting; - textMateRules?: ITokenColorizationRule[]; + textMateRules?: ITextMateThemingRule[]; } -export interface ITokenColorizationRule { +export interface IExperimentalTokenStyleCustomizations { + [styleRuleOrThemeSettingsId: string]: string | ITokenColorizationSetting | IExperimentalTokenStyleCustomizations | undefined; +} + +export interface ITextMateThemingRule { name?: string; scope?: string | string[]; settings: ITokenColorizationSetting; @@ -89,7 +94,7 @@ export interface ITokenColorizationRule { export interface ITokenColorizationSetting { foreground?: string; background?: string; - fontStyle?: string; // italic, underline, bold + fontStyle?: string; /* [italic|underline|bold] */ } export interface ExtensionData { @@ -106,4 +111,4 @@ export interface IThemeExtensionPoint { path: string; uiTheme?: typeof VS_LIGHT_THEME | typeof VS_DARK_THEME | typeof VS_HC_THEME; _watch: boolean; // unsupported options to watch location -} \ No newline at end of file +} 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 a0fe30b8683..ab41c917cb8 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, TokenStylingRule } from 'vs/platform/theme/common/tokenClassificationRegistry'; +import { TokenStyle, comments, variables, types, functions, keywords, numbers, strings, 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'; @@ -44,14 +44,6 @@ function tokenStyleAsString(ts: TokenStyle | undefined | null) { return str; } -function getTokenStyleRules(rules: [string, TokenStyle][]): TokenStylingRule[] { - return rules.map(e => { - const rule = tokenClassificationRegistry.getTokenStylingRule(e[0], e[1]); - assert.ok(rule); - return rule!; - }); -} - function assertTokenStyle(actual: TokenStyle | undefined | null, expected: TokenStyle | undefined | null, message?: string) { assert.equal(tokenStyleAsString(actual), tokenStyleAsString(expected), message); } @@ -87,7 +79,7 @@ suite('Themes - TokenStyleResolving', () => { assertTokenStyles(themeData, { [comments]: ts('#88846f', undefinedStyle), [variables]: ts('#F8F8F2', unsetStyle), - [types]: ts('#A6E22E', { underline: true, bold: false, italic: false }), + [types]: ts('#A6E22E', { underline: true }), [functions]: ts('#A6E22E', unsetStyle), [strings]: ts('#E6DB74', undefinedStyle), [numbers]: ts('#AE81FF', undefinedStyle), @@ -187,7 +179,7 @@ suite('Themes - TokenStyleResolving', () => { assertTokenStyles(themeData, { [comments]: ts('#384887', undefinedStyle), [variables]: ts(undefined, unsetStyle), - [types]: ts('#ffeebb', { underline: true, bold: false, italic: false }), + [types]: ts('#ffeebb', { underline: true }), [functions]: ts('#ddbb88', unsetStyle), [strings]: ts('#22aa44', undefinedStyle), [numbers]: ts('#f280d0', undefinedStyle), @@ -259,43 +251,43 @@ suite('Themes - TokenStyleResolving', () => { assertTokenStyle(tokenStyle, defaultTokenStyle, 'keyword.operators'); tokenStyle = themeData.resolveScopes([['storage']]); - assertTokenStyle(tokenStyle, ts('#F92672', { italic: true, underline: false, bold: false }), 'storage'); + assertTokenStyle(tokenStyle, ts('#F92672', { italic: true }), 'storage'); tokenStyle = themeData.resolveScopes([['storage.type']]); - assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true, underline: false, bold: false }), 'storage.type'); + assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true }), 'storage.type'); tokenStyle = themeData.resolveScopes([['entity.name.class']]); - assertTokenStyle(tokenStyle, ts('#A6E22E', { underline: true, italic: false, bold: false }), 'entity.name.class'); + assertTokenStyle(tokenStyle, ts('#A6E22E', { underline: true }), 'entity.name.class'); tokenStyle = themeData.resolveScopes([['meta.structure.dictionary.json', 'string.quoted.double.json']]); assertTokenStyle(tokenStyle, ts('#66D9EF', undefined), 'json property'); tokenStyle = themeData.resolveScopes([['keyword'], ['storage.type'], ['entity.name.class']]); - assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true, underline: false, bold: false }), 'storage.type'); + assertTokenStyle(tokenStyle, ts('#66D9EF', { italic: true }), 'storage.type'); }); test('rule matching', async () => { const themeData = ColorThemeData.createLoadedEmptyTheme('test', 'test'); themeData.setCustomColors({ 'editor.foreground': '#000000' }); - themeData.setTokenStyleRules(getTokenStyleRules([ - ['types', ts('#ff0000', undefined)], - ['classes', ts('#0000ff', { italic: true })], - ['*.static', ts(undefined, { bold: true })], - ['*.declaration', ts(undefined, { italic: true })], - ['*.async.static', ts('#00ffff', { bold: false, underline: true })], - ['*.async', ts('#000fff', { italic: false, underline: true })] - ])); + themeData.setCustomTokenStyleRules({ + 'types': '#ff0000', + 'classes': { foreground: '#0000ff', fontStyle: 'italic' }, + '*.static': { fontStyle: 'bold' }, + '*.declaration': { fontStyle: 'italic' }, + '*.async.static': { fontStyle: 'italic underline' }, + '*.async': { foreground: '#000fff', fontStyle: '-italic underline' } + }); assertTokenStyles(themeData, { 'types': ts('#ff0000', undefinedStyle), - 'types.static': ts('#ff0000', { bold: true, italic: undefined, underline: undefined }), - 'types.static.declaration': ts('#ff0000', { bold: true, italic: true, underline: undefined }), - 'classes': ts('#0000ff', { bold: undefined, italic: true, underline: undefined }), - 'classes.static.declaration': ts('#0000ff', { bold: true, italic: true, underline: undefined }), - 'classes.declaration': ts('#0000ff', { bold: undefined, italic: true, underline: undefined }), - 'classes.declaration.async': ts('#000fff', { bold: undefined, italic: false, underline: true }), - 'classes.declaration.async.static': ts('#00ffff', { bold: false, italic: false, underline: true }), + '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 }), }); }); From dfe469fb985ae8e6686125039994983e815f7d28 Mon Sep 17 00:00:00 2001 From: isidor Date: Mon, 11 Nov 2019 16:05:00 +0100 Subject: [PATCH 34/49] fixes #83396 --- .../workbench/contrib/files/browser/views/explorerViewer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index af298d30f03..054a39beafe 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -751,9 +751,9 @@ export class FileDragAndDrop implements ITreeDragAndDrop { if (confirmDragAndDrop) { const confirmation = await this.dialogService.confirm({ message: items.length > 1 && items.every(s => s.isRoot) ? localize('confirmRootsMove', "Are you sure you want to change the order of multiple root folders in your workspace?") - : items.length > 1 ? getConfirmMessage(localize('confirmMultiMove', "Are you sure you want to move the following {0} files?", items.length), items.map(s => s.resource)) + : items.length > 1 ? getConfirmMessage(localize('confirmMultiMove', "Are you sure you want to move the following {0} files into '{1}'?", items.length, target.name), items.map(s => s.resource)) : items[0].isRoot ? localize('confirmRootMove', "Are you sure you want to change the order of root folder '{0}' in your workspace?", items[0].name) - : localize('confirmMove', "Are you sure you want to move '{0}'?", items[0].name), + : localize('confirmMove', "Are you sure you want to move '{0}' into '{1}'?", items[0].name, target.name), checkbox: { label: localize('doNotAskAgain', "Do not ask me again") }, From c27fe98afec4d5d7ff23ba076e2cdb39eab93f38 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 11 Nov 2019 16:22:56 +0100 Subject: [PATCH 35/49] Reduce typings files (#83421) --- package.json | 3 ++- remote/package.json | 4 ++-- remote/yarn.lock | 16 +++++++-------- src/typings/http-proxy-agent.d.ts | 20 ------------------- src/typings/vscode-proxy-agent.d.ts | 6 ------ src/vs/platform/request/node/proxy.ts | 2 +- .../services/extensions/node/proxyResolver.ts | 10 +++++++--- yarn.lock | 15 ++++++++++---- 8 files changed, 31 insertions(+), 45 deletions(-) delete mode 100644 src/typings/http-proxy-agent.d.ts delete mode 100644 src/typings/vscode-proxy-agent.d.ts diff --git a/package.json b/package.json index ab2174a9025..d4bf37ca9ae 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "v8-inspect-profiler": "^0.0.20", "vscode-minimist": "^1.2.1", "vscode-nsfw": "1.2.8", - "vscode-proxy-agent": "^0.5.1", + "vscode-proxy-agent": "^0.5.2", "vscode-ripgrep": "^1.5.7", "vscode-sqlite3": "4.0.9", "vscode-textmate": "^4.3.0", @@ -65,6 +65,7 @@ "@types/chokidar": "2.1.3", "@types/cookie": "^0.3.3", "@types/graceful-fs": "4.1.2", + "@types/http-proxy-agent": "^2.0.1", "@types/iconv-lite": "0.0.1", "@types/keytar": "^4.4.0", "@types/mocha": "2.2.39", diff --git a/remote/package.json b/remote/package.json index 439fd388f8c..ec299bfed0c 100644 --- a/remote/package.json +++ b/remote/package.json @@ -17,7 +17,7 @@ "spdlog": "^0.11.1", "vscode-minimist": "^1.2.1", "vscode-nsfw": "1.2.8", - "vscode-proxy-agent": "^0.5.1", + "vscode-proxy-agent": "^0.5.2", "vscode-ripgrep": "^1.5.7", "vscode-textmate": "^4.3.0", "xterm": "4.3.0-beta17", @@ -27,7 +27,7 @@ "yazl": "^2.4.3" }, "optionalDependencies": { - "vscode-windows-ca-certs": "0.1.0", + "vscode-windows-ca-certs": "0.2.0", "vscode-windows-registry": "1.0.2" } } diff --git a/remote/yarn.lock b/remote/yarn.lock index c6e5ac0c6b6..1aab9f6a9c0 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -384,10 +384,10 @@ vscode-nsfw@1.2.8: lodash.isundefined "^3.0.1" nan "^2.10.0" -vscode-proxy-agent@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.1.tgz#7fd15e157c02176a0dca9f87840ad0991a62ca57" - integrity sha512-Nnkc7gBk9iAbbZURYZm3p/wvDvRVwDvdzEvDqF1Jh40p6przwQU/JTlV1XLrmd4cCwh6TOAWD81Z3cq+K7Xdmw== +vscode-proxy-agent@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.2.tgz#0c90d24d353957b841d741da7b2701e3f0a044c4" + integrity sha512-1cCNPxrWIrmUwS+1XGaXxkh3G1y7z2fpXl1sT74OZvELaryQWYb3NMxMLJJ4Q/CpPLEyuhp/bAN7nzHxxFcQ5Q== dependencies: debug "^3.1.0" http-proxy-agent "^2.1.0" @@ -406,10 +406,10 @@ vscode-textmate@^4.3.0: dependencies: oniguruma "^7.2.0" -vscode-windows-ca-certs@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/vscode-windows-ca-certs/-/vscode-windows-ca-certs-0.1.0.tgz#d58eeb40b536130918cfde2b01e6dc7e5c1bd757" - integrity sha512-ZfZbfJIE09Q0dwGqmqTj7kuAq4g6lul9WPJvo0DkKjln8/FL+dY3wUKIKbYwWQp4x56SBTLBq3tJkD72xQ9Gqw== +vscode-windows-ca-certs@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/vscode-windows-ca-certs/-/vscode-windows-ca-certs-0.2.0.tgz#086f0f4de57e2760a35ac6920831bff246237115" + integrity sha512-YBrJRT0zos+Yb1Qdn73GD8QZr7pa2IE96b5Y1hmmp6XeR8aYB7Iiq5gDAF/+/AxL+caSR9KPZQ6jiYWh5biD7w== dependencies: node-addon-api "1.6.2" diff --git a/src/typings/http-proxy-agent.d.ts b/src/typings/http-proxy-agent.d.ts deleted file mode 100644 index 3d0071543b4..00000000000 --- a/src/typings/http-proxy-agent.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'http-proxy-agent' { - - interface IHttpProxyAgentOptions { - host: string; - port: number; - auth?: string; - } - - class HttpProxyAgent { - constructor(proxy: string); - constructor(opts: IHttpProxyAgentOptions); - } - - export = HttpProxyAgent; -} \ No newline at end of file diff --git a/src/typings/vscode-proxy-agent.d.ts b/src/typings/vscode-proxy-agent.d.ts deleted file mode 100644 index a997fa97801..00000000000 --- a/src/typings/vscode-proxy-agent.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode-proxy-agent'; diff --git a/src/vs/platform/request/node/proxy.ts b/src/vs/platform/request/node/proxy.ts index 116f7e0c428..30b5bc29a9a 100644 --- a/src/vs/platform/request/node/proxy.ts +++ b/src/vs/platform/request/node/proxy.ts @@ -39,7 +39,7 @@ export async function getProxyAgent(rawRequestURL: string, options: IOptions = { const opts = { host: proxyEndpoint.hostname || '', - port: Number(proxyEndpoint.port), + port: proxyEndpoint.port || (proxyEndpoint.protocol === 'https' ? '443' : '80'), auth: proxyEndpoint.auth, rejectUnauthorized: isBoolean(options.strictSSL) ? options.strictSSL : true }; diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 64c2e0a526e..af9bcd2721c 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -343,9 +343,13 @@ function patches(originals: typeof http | typeof https, resolveProxy: ReturnType return original.apply(null, arguments as unknown as any[]); } - const optionsPatched = options.agent instanceof ProxyAgent; + const originalAgent = options.agent; + if (originalAgent === true) { + throw new Error('Unexpected agent option: true'); + } + const optionsPatched = originalAgent instanceof ProxyAgent; const config = onRequest && ((options)._vscodeProxySupport || /* LS */ (options)._vscodeSystemProxy) || proxySetting.config; - const useProxySettings = !optionsPatched && (config === 'override' || config === 'on' && !options.agent); + const useProxySettings = !optionsPatched && (config === 'override' || config === 'on' && originalAgent === undefined); const useSystemCertificates = !optionsPatched && certSetting.config && originals === https && !(options as https.RequestOptions).ca; if (useProxySettings || useSystemCertificates) { @@ -367,7 +371,7 @@ function patches(originals: typeof http | typeof https, resolveProxy: ReturnType options.agent = new ProxyAgent({ resolveProxy: resolveProxy.bind(undefined, { useProxySettings, useSystemCertificates }), defaultPort: originals === https ? 443 : 80, - originalAgent: options.agent + originalAgent }); return original(options, callback); } diff --git a/yarn.lock b/yarn.lock index e3aff8c82d8..5e44523b3f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -133,6 +133,13 @@ dependencies: "@types/node" "*" +"@types/http-proxy-agent@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/http-proxy-agent/-/http-proxy-agent-2.0.1.tgz#2f95077f6bfe7adc39cc0f0042da85997ae77fc7" + integrity sha512-dgsgbsgI3t+ZkdzF9H19uBaLsurIZJJjJsVpj4mCLp8B6YghQ7jVwyqhaL0PcVtuC3nOi0ZBhAi2Dd9jCUwdFA== + dependencies: + "@types/node" "*" + "@types/iconv-lite@0.0.1": version "0.0.1" resolved "https://registry.yarnpkg.com/@types/iconv-lite/-/iconv-lite-0.0.1.tgz#aa3b8bda2be512b1ae0a057b942e869c370a5569" @@ -9033,10 +9040,10 @@ vscode-nsfw@1.2.8: lodash.isundefined "^3.0.1" nan "^2.10.0" -vscode-proxy-agent@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.1.tgz#7fd15e157c02176a0dca9f87840ad0991a62ca57" - integrity sha512-Nnkc7gBk9iAbbZURYZm3p/wvDvRVwDvdzEvDqF1Jh40p6przwQU/JTlV1XLrmd4cCwh6TOAWD81Z3cq+K7Xdmw== +vscode-proxy-agent@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.2.tgz#0c90d24d353957b841d741da7b2701e3f0a044c4" + integrity sha512-1cCNPxrWIrmUwS+1XGaXxkh3G1y7z2fpXl1sT74OZvELaryQWYb3NMxMLJJ4Q/CpPLEyuhp/bAN7nzHxxFcQ5Q== dependencies: debug "^3.1.0" http-proxy-agent "^2.1.0" From a95e14d1cf88ffb8571966a39b30f35d887e0ac6 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 11 Nov 2019 17:05:27 +0100 Subject: [PATCH 36/49] Reduce typings files (#83421) --- src/typings/vscode-windows-ca-certs.d.ts | 6 ------ src/vs/workbench/services/extensions/node/proxyResolver.ts | 4 +++- 2 files changed, 3 insertions(+), 7 deletions(-) delete mode 100644 src/typings/vscode-windows-ca-certs.d.ts diff --git a/src/typings/vscode-windows-ca-certs.d.ts b/src/typings/vscode-windows-ca-certs.d.ts deleted file mode 100644 index f923eb8a8ac..00000000000 --- a/src/typings/vscode-windows-ca-certs.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode-windows-ca-certs'; diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index af9bcd2721c..1bb31127b8c 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -473,7 +473,9 @@ async function readCaCertificates() { } async function readWindowsCaCertificates() { - const winCA = await import('vscode-windows-ca-certs'); + const winCA = await new Promise((resolve, reject) => { + require(['vscode-windows-ca-certs'], resolve, reject); + }); let ders: any[] = []; const store = winCA(); From cec8079c59d66374e1b20d97a4ad7a1743d74008 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 11 Nov 2019 17:26:19 +0100 Subject: [PATCH 37/49] opener - encapsulate external opening --- .../editor/browser/services/openerService.ts | 32 ++++++++++++------- src/vs/platform/opener/common/opener.ts | 24 ++++++++++++-- src/vs/workbench/electron-browser/window.ts | 32 ++++++------------- 3 files changed, 53 insertions(+), 35 deletions(-) diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 9902634b3e2..48175adfb42 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -13,7 +13,7 @@ import { equalsIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; -import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions } from 'vs/platform/opener/common/opener'; +import { IOpener, IOpenerService, IValidator, IExternalUriResolver, OpenOptions, ResolveExternalUriOptions, IResolvedExternalUri, IExternalOpener } from 'vs/platform/opener/common/opener'; import { EditorOpenContext } from 'vs/platform/editor/common/editor'; export class OpenerService extends Disposable implements IOpenerService { @@ -23,12 +23,22 @@ export class OpenerService extends Disposable implements IOpenerService { private readonly _openers = new LinkedList(); private readonly _validators = new LinkedList(); private readonly _resolvers = new LinkedList(); + private _externalOpener: IExternalOpener; constructor( @ICodeEditorService private readonly _editorService: ICodeEditorService, @ICommandService private readonly _commandService: ICommandService, ) { super(); + + // Default external opener is going through window.open() + this._externalOpener = { + openExternal: href => { + dom.windowOpenNoOpener(href); + + return Promise.resolve(true); + } + }; } registerOpener(opener: IOpener): IDisposable { @@ -49,6 +59,10 @@ export class OpenerService extends Disposable implements IOpenerService { return { dispose: remove }; } + setExternalOpener(externalOpener: IExternalOpener): void { + this._externalOpener = externalOpener; + } + async open(resource: URI, options?: OpenOptions): Promise { // no scheme ?!? @@ -75,7 +89,7 @@ export class OpenerService extends Disposable implements IOpenerService { return this._doOpen(resource, options); } - async resolveExternalUri(resource: URI, options?: { readonly allowTunneling?: boolean }): Promise<{ resolved: URI, dispose(): void }> { + async resolveExternalUri(resource: URI, options?: ResolveExternalUriOptions): Promise { for (const resolver of this._resolvers.toArray()) { const result = await resolver.resolveExternalUri(resource, options); if (result) { @@ -89,13 +103,8 @@ export class OpenerService extends Disposable implements IOpenerService { private async _doOpen(resource: URI, options: OpenOptions | undefined): Promise { const { scheme, path, query, fragment } = resource; - if (equalsIgnoreCase(scheme, Schemas.mailto) || options?.openExternal) { - // open default mail application - return this._doOpenExternal(resource, options); - } - - if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https)) { - // open link in default browser + if (options?.openExternal || equalsIgnoreCase(scheme, Schemas.mailto) || equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https)) { + // open externally return this._doOpenExternal(resource, options); } @@ -149,9 +158,10 @@ export class OpenerService extends Disposable implements IOpenerService { private async _doOpenExternal(resource: URI, options: OpenOptions | undefined): Promise { const { resolved } = await this.resolveExternalUri(resource, options); - dom.windowOpenNoOpener(encodeURI(resolved.toString(true))); - return true; + // TODO@Jo neither encodeURI nor toString(true) should be needed + // once we go with URL and not URI + return this._externalOpener.openExternal(encodeURI(resolved.toString(true))); } dispose() { diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index 70377fcb3da..3b60521677a 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -28,11 +28,21 @@ type OpenExternalOptions = { readonly openExternal?: boolean; readonly allowTunn export type OpenOptions = OpenInternalOptions & OpenExternalOptions; +export type ResolveExternalUriOptions = { readonly allowTunneling?: boolean }; + +export interface IResolvedExternalUri extends IDisposable { + resolved: URI; +} + export interface IOpener { open(resource: URI, options?: OpenInternalOptions): Promise; open(resource: URI, options?: OpenExternalOptions): Promise; } +export interface IExternalOpener { + openExternal(href: string): Promise; +} + export interface IValidator { shouldOpen(resource: URI): Promise; } @@ -61,6 +71,12 @@ export interface IOpenerService { */ registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable; + /** + * Sets the handler for opening externally. If not provided, + * a default handler will be used. + */ + setExternalOpener(opener: IExternalOpener): void; + /** * Opens a resource, like a webaddress, a document uri, or executes command. * @@ -70,7 +86,10 @@ export interface IOpenerService { open(resource: URI, options?: OpenInternalOptions): Promise; open(resource: URI, options?: OpenExternalOptions): Promise; - resolveExternalUri(resource: URI, options?: { readonly allowTunneling?: boolean }): Promise<{ resolved: URI, dispose(): void }>; + /** + * Resolve a resource to its external form. + */ + resolveExternalUri(resource: URI, options?: ResolveExternalUriOptions): Promise; } export const NullOpenerService: IOpenerService = Object.freeze({ @@ -78,6 +97,7 @@ export const NullOpenerService: IOpenerService = Object.freeze({ registerOpener() { return Disposable.None; }, registerValidator() { return Disposable.None; }, registerExternalUriResolver() { return Disposable.None; }, - open() { return Promise.resolve(false); }, + setExternalOpener() { }, + async open() { return false; }, async resolveExternalUri(uri: URI) { return { resolved: uri, dispose() { } }; }, }); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index d5933e71057..9f58a3e4601 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -398,29 +398,23 @@ export class ElectronWindow extends Disposable { throw new Error('Prevented call to window.open(). Use IOpenerService instead!'); }; - // Handle internal open() calls - this.openerService.registerOpener({ - open: async (resource: URI, options?: OpenOptions): Promise => { - - // If either the caller wants to open externally or the - // scheme is one where we prefer to open externally - // we handle this resource by delegating the opening to - // the main process to prevent window focus issues. - if (this.shouldOpenExternal(resource, options)) { - const { resolved } = await this.openerService.resolveExternalUri(resource, options); - const success = await this.electronService.openExternal(encodeURI(resolved.toString(true))); - if (!success && resolved.scheme === Schemas.file) { + // Handle external open() calls + this.openerService.setExternalOpener({ + openExternal: async (href: string) => { + const success = await this.electronService.openExternal(href); + if (!success) { + const fileCandidate = URI.parse(href); + if (fileCandidate.scheme === Schemas.file) { // if opening failed, and this is a file, we can still try to reveal it - await this.electronService.showItemInFolder(resolved.fsPath); + await this.electronService.showItemInFolder(fileCandidate.fsPath); } - - return true; } - return false; // not handled by us + return true; } }); + // Register external URI resolver this.openerService.registerExternalUriResolver({ resolveExternalUri: async (uri: URI, options?: OpenOptions) => { if (options?.allowTunneling) { @@ -440,12 +434,6 @@ export class ElectronWindow extends Disposable { }); } - private shouldOpenExternal(resource: URI, options?: OpenOptions) { - const scheme = resource.scheme.toLowerCase(); - const preferOpenExternal = (scheme === Schemas.mailto || scheme === Schemas.http || scheme === Schemas.https); - return options?.openExternal || preferOpenExternal; - } - private updateTouchbarMenu(): void { if (!isMacintosh) { return; // macOS only From 33e5678363c20a7ac6665cc88752ee00fc83cc0d Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 11 Nov 2019 17:28:26 +0100 Subject: [PATCH 38/49] Change remote explorer to use drop-down UI. (#84482) Part of https://github.com/microsoft/vscode-remote-release/issues/1778 --- src/vs/workbench/browser/parts/views/views.ts | 5 + .../browser/parts/views/viewsViewlet.ts | 113 +++++++++++++++++- .../remote/browser/explorerViewItems.ts | 106 ++++++++++++++++ .../contrib/remote/browser/remote.ts | 79 ++++++------ .../contrib/remote/browser/remoteViewlet.css | 19 +++ .../remote/common/remote.contribution.ts | 2 +- .../remote/common/remoteExplorerService.ts | 36 ++++++ src/vs/workbench/workbench.common.main.ts | 1 + 8 files changed, 316 insertions(+), 45 deletions(-) create mode 100644 src/vs/workbench/contrib/remote/browser/explorerViewItems.ts create mode 100644 src/vs/workbench/services/remote/common/remoteExplorerService.ts diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 9a0a307dd5c..a0981c5c296 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -240,6 +240,9 @@ export class ContributableViewsModel extends Disposable { private _onDidChangeViewState = this._register(new Emitter()); protected readonly onDidChangeViewState: Event = this._onDidChangeViewState.event; + private _onDidChangeActiveViews = this._register(new Emitter()); + readonly onDidChangeActiveViews: Event = this._onDidChangeActiveViews.event; + constructor( container: ViewContainer, viewsService: IViewsService, @@ -469,6 +472,8 @@ export class ContributableViewsModel extends Disposable { if (toAdd.length) { this._onDidAdd.fire(toAdd); } + + this._onDidChangeActiveViews.fire(this.viewDescriptors); } } diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 239514051a2..aa4e7f432ed 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -23,7 +23,7 @@ import { DefaultPanelDndController } from 'vs/base/browser/ui/splitview/panelvie import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService'; import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; -import { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { localize } from 'vs/nls'; @@ -112,7 +112,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView })); result.push(...viewToggleActions); - const parentActions = super.getContextMenuActions(); + const parentActions = this.getViewletContextMenuActions(); if (viewToggleActions.length && parentActions.length) { result.push(new Separator()); } @@ -121,6 +121,10 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView return result; } + protected getViewletContextMenuActions() { + return super.getContextMenuActions(); + } + setVisible(visible: boolean): void { super.setVisible(visible); this.panels.filter(view => view.isVisible() !== visible) @@ -264,7 +268,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView }); } - private toggleViewVisibility(viewId: string): void { + protected toggleViewVisibility(viewId: string): void { const visible = !this.viewsModel.isVisible(viewId); type ViewsToggleVisibilityClassification = { viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; @@ -321,6 +325,109 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView } } +export abstract class FilterViewContainerViewlet extends ViewContainerViewlet { + private constantViewDescriptors: Map = new Map(); + private allViews: Map> = new Map(); + private filterValue: string; + + protected onDidChangeFilterValue: Emitter = new Emitter(); + + constructor( + viewletId: string, + @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IWorkspaceContextService contextService: IWorkspaceContextService + ) { + super(viewletId, `${viewletId}.state`, false, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + this._register(this.onDidChangeFilterValue.event(newFilterValue => { + this.filterValue = newFilterValue; + this.onFilterChanged(newFilterValue); + })); + + this._register(this.viewsModel.onDidChangeActiveViews((viewDescriptors) => { + viewDescriptors.forEach(descriptor => { + let filterOnValue = this.getFilterOn(descriptor); + if (!filterOnValue) { + return; + } + if (!this.allViews.has(filterOnValue)) { + this.allViews.set(filterOnValue, new Map()); + } + this.allViews.get(filterOnValue)!.set(descriptor.id, descriptor); + if (filterOnValue !== this.filterValue) { + this.viewsModel.setVisible(descriptor.id, false); + } + }); + })); + } + + protected addConstantViewDescriptors(constantViewDescriptors: IViewDescriptor[]) { + constantViewDescriptors.forEach(viewDescriptor => this.constantViewDescriptors.set(viewDescriptor.id, viewDescriptor)); + } + + protected abstract getFilterOn(viewDescriptor: IViewDescriptor): string | undefined; + + private onFilterChanged(newFilterValue: string) { + this.getViewsNotForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, false)); + this.getViewsForTarget(newFilterValue).forEach(item => this.viewsModel.setVisible(item.id, true)); + } + + getContextMenuActions(): IAction[] { + const result: IAction[] = []; + let viewToggleActions: IAction[] = Array.from(this.constantViewDescriptors.values()).map(viewDescriptor => ({ + id: `${viewDescriptor.id}.toggleVisibility`, + label: viewDescriptor.name, + checked: this.viewsModel.isVisible(viewDescriptor.id), + enabled: viewDescriptor.canToggleVisibility, + run: () => this.toggleViewVisibility(viewDescriptor.id) + })); + + result.push(...viewToggleActions); + const parentActions = this.getViewletContextMenuActions(); + if (viewToggleActions.length && parentActions.length) { + result.push(new Separator()); + } + + result.push(...parentActions); + return result; + } + + private getViewsForTarget(target: string): IViewDescriptor[] { + return this.allViews.has(target) ? Array.from(this.allViews.get(target)!.values()) : []; + } + + private getViewsNotForTarget(target: string): IViewDescriptor[] { + const iterable = this.allViews.keys(); + let key = iterable.next(); + let views: IViewDescriptor[] = []; + while (!key.done) { + if (key.value !== target) { + views = views.concat(this.getViewsForTarget(key.value)); + } + key = iterable.next(); + } + return views; + } + + onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { + const panels: ViewletPanel[] = super.onDidAddViews(added); + for (let i = 0; i < added.length; i++) { + if (this.constantViewDescriptors.has(added[i].viewDescriptor.id)) { + panels[i].setExpanded(false); + } + } + return panels; + } + + abstract getTitle(): string; +} + export class FileIconThemableWorkbenchTree extends WorkbenchTree { constructor( diff --git a/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts new file mode 100644 index 00000000000..f57014a69b5 --- /dev/null +++ b/src/vs/workbench/contrib/remote/browser/explorerViewItems.ts @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; + +import { IActionRunner, IAction, Action } from 'vs/base/common/actions'; +import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { selectBorder } from 'vs/platform/theme/common/colorRegistry'; +import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; +import { IViewDescriptor } from 'vs/workbench/common/views'; +import { startsWith } from 'vs/base/common/strings'; +import { isStringArray } from 'vs/base/common/types'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; + +export interface IRemoteSelectItem extends ISelectOptionItem { + authority: string[]; +} + +export class SwitchRemoteViewItem extends SelectActionViewItem { + + actionRunner!: IActionRunner; + + constructor( + action: IAction, + private readonly optionsItems: IRemoteSelectItem[], + @IThemeService private readonly themeService: IThemeService, + @IContextViewService contextViewService: IContextViewService, + @IRemoteExplorerService remoteExplorerService: IRemoteExplorerService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService + ) { + super(null, action, optionsItems, 0, contextViewService, { ariaLabel: nls.localize('remotes', 'Switch Remote') }); + this._register(attachSelectBoxStyler(this.selectBox, themeService, { + selectBackground: SIDE_BAR_BACKGROUND + })); + + this.setSelectionForConnection(optionsItems, environmentService, remoteExplorerService); + } + + private setSelectionForConnection(optionsItems: IRemoteSelectItem[], environmentService: IWorkbenchEnvironmentService, remoteExplorerService: IRemoteExplorerService) { + // TODO: set from saved state + if (this.optionsItems.length > 0) { + const remoteAuthority = environmentService.configuration.remoteAuthority; + let index = 0; + if (remoteAuthority) { + const actualRemoteAuthority = remoteAuthority.split('+')[0]; + for (let optionIterator = 0; (optionIterator < this.optionsItems.length) && (index === 0); optionIterator++) { + for (let authorityIterator = 0; authorityIterator < optionsItems[optionIterator].authority.length; authorityIterator++) { + if (optionsItems[optionIterator].authority[authorityIterator] === actualRemoteAuthority) { + index = optionIterator; + break; + } + } + } + } + this.select(index); + remoteExplorerService.targetType = optionsItems[index].authority[0]; + } + } + + render(container: HTMLElement) { + super.render(container); + dom.addClass(container, 'switch-remote'); + this._register(attachStylerCallback(this.themeService, { selectBorder }, colors => { + container.style.border = colors.selectBorder ? `1px solid ${colors.selectBorder}` : ''; + })); + } + + protected getActionContext(_: string, index: number): any { + return this.optionsItems[index]; + } + + static createOptionItems(views: IViewDescriptor[]): IRemoteSelectItem[] { + let options: IRemoteSelectItem[] = []; + views.forEach(view => { + if (view.group && startsWith(view.group, 'targets') && view.remoteAuthority) { + options.push({ text: view.name, authority: isStringArray(view.remoteAuthority) ? view.remoteAuthority : [view.remoteAuthority] }); + } + }); + return options; + } +} + +export class SwitchRemoteAction extends Action { + + public static readonly ID = 'remote.explorer.switch'; + public static readonly LABEL = nls.localize('remote.explorer.switch', "Switch Remote"); + + constructor( + id: string, label: string, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService + ) { + super(id, label); + } + + public async run(item: IRemoteSelectItem): Promise { + this.remoteExplorerService.targetType = item.authority[0]; + } +} diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 4d17af30bd3..f87cdc9515a 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -16,10 +16,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { FilterViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { VIEWLET_ID, VIEW_CONTAINER } from 'vs/workbench/contrib/remote/common/remote.contribution'; import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; -import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IViewDescriptor, IViewsRegistry, Extensions } from 'vs/workbench/common/views'; @@ -47,7 +46,10 @@ import Severity from 'vs/base/common/severity'; import { ReloadWindowAction } from 'vs/workbench/browser/actions/windowActions'; import { IDisposable } from 'vs/base/common/lifecycle'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { SwitchRemoteViewItem, SwitchRemoteAction } from 'vs/workbench/contrib/remote/browser/explorerViewItems'; +import { Action, IActionViewItem, IAction } from 'vs/base/common/actions'; +import { isStringArray } from 'vs/base/common/types'; +import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; interface HelpInformation { extensionDescription: IExtensionDescription; @@ -364,10 +366,9 @@ class HelpPanelDescriptor implements IViewDescriptor { } } - -export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { +export class RemoteViewlet extends FilterViewContainerViewlet implements IViewModel { private helpPanelDescriptor = new HelpPanelDescriptor(this); - + private actions: IAction[] | undefined; helpInformations: HelpInformation[] = []; constructor( @@ -380,10 +381,10 @@ export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); - + super(VIEWLET_ID, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + this.addConstantViewDescriptors([this.helpPanelDescriptor]); remoteHelpExtPoint.setHandler((extensions) => { let helpInformation: HelpInformation[] = []; for (let extension of extensions) { @@ -399,6 +400,34 @@ export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { viewsRegistry.deregisterViews([this.helpPanelDescriptor], VIEW_CONTAINER); } }); + + this._register(this.remoteExplorerService.onDidChangeTargetType(() => { + this.onDidChangeFilterValue.fire(this.remoteExplorerService.targetType); + })); + } + + protected getFilterOn(viewDescriptor: IViewDescriptor): string | undefined { + return isStringArray(viewDescriptor.remoteAuthority) ? viewDescriptor.remoteAuthority[0] : viewDescriptor.remoteAuthority; + } + + 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 super.getActionViewItem(action); + } + + public getActions(): IAction[] { + if (!this.actions) { + this.actions = [ + this.instantiationService.createInstance(SwitchRemoteAction, SwitchRemoteAction.ID, SwitchRemoteAction.LABEL), + ]; + this.actions.forEach(a => { + this._register(a); + }); + } + return this.actions; } private _handleRemoteInfoExtensionPoint(extension: IExtensionPointUser, helpInformation: HelpInformation[]) { @@ -419,38 +448,6 @@ export class RemoteViewlet extends ViewContainerViewlet implements IViewModel { }); } - onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPanel[] { - // too late, already added to the view model - const result = super.onDidAddViews(added); - - const remoteAuthority = this.environmentService.configuration.remoteAuthority; - if (remoteAuthority) { - const actualRemoteAuthority = remoteAuthority.split('+')[0]; - added.forEach((descriptor) => { - const panel = this.getView(descriptor.viewDescriptor.id); - if (!panel) { - return; - } - - const descriptorAuthority = descriptor.viewDescriptor.remoteAuthority; - if (typeof descriptorAuthority === 'undefined') { - panel.setExpanded(true); - } else if (descriptor.viewDescriptor.id === HelpPanel.ID) { - // Do nothing, keep the default behavior for Help - } else { - const descriptorAuthorityArr = Array.isArray(descriptorAuthority) ? descriptorAuthority : [descriptorAuthority]; - if (descriptorAuthorityArr.indexOf(actualRemoteAuthority) >= 0) { - panel.setExpanded(true); - } else { - panel.setExpanded(false); - } - } - }); - } - - return result; - } - getTitle(): string { const title = nls.localize('remote.explorer', "Remote Explorer"); return title; diff --git a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css index 6f6908846c3..d48d027c617 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteViewlet.css +++ b/src/vs/workbench/contrib/remote/browser/remoteViewlet.css @@ -87,3 +87,22 @@ .hc-black .monaco-list .monaco-list-row .remote-help-tree-node-item>.remote-help-tree-node-item-icon.issueReporter { background-image: url('help-report-issue-hc.svg') } + +.monaco-workbench .part > .title > .title-actions .switch-remote { + display: flex; + align-items: center; + font-size: 11px; + margin-right: 0.3em; + height: 20px; + flex-shrink: 1; + margin-top: 7px; +} + +.switch-remote > .monaco-select-box { + border: none; + display: block; +} + +.monaco-workbench .part > .title > .title-actions .switch-remote > .monaco-select-box { + padding-left: 3px; +} diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 43671fbda5e..5d12781e767 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -34,7 +34,7 @@ export const VIEW_CONTAINER: ViewContainer = Registry.as('remoteExplorerService'); + +export interface IRemoteExplorerService { + _serviceBrand: undefined; + onDidChangeTargetType: Event; + targetType: string; +} + +class RemoteExplorerService implements IRemoteExplorerService { + public _serviceBrand: undefined; + private _targetType: string = ''; + private _onDidChangeTargetType: Emitter = new Emitter(); + public onDidChangeTargetType: Event = this._onDidChangeTargetType.event; + + set targetType(name: string) { + if (this._targetType !== name) { + const oldTarget = this._targetType; + this._targetType = name; + this._onDidChangeTargetType.fire(oldTarget); + } + } + get targetType(): string { + return this._targetType; + } +} + +registerSingleton(IRemoteExplorerService, RemoteExplorerService, true); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 42cf7aaffd7..ddb19940d86 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -81,6 +81,7 @@ import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/extensions/common/staticExtensions'; import 'vs/workbench/services/userDataSync/common/settingsMergeService'; import 'vs/workbench/services/path/common/remotePathService'; +import 'vs/workbench/services/remote/common/remoteExplorerService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; From d1d9128573f76c19a55af28def98da5567198362 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 11 Nov 2019 17:49:57 +0100 Subject: [PATCH 39/49] Reduce typings files (#83421) --- src/vs/code/test/electron-main/nativeHelpers.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/code/test/electron-main/nativeHelpers.test.ts b/src/vs/code/test/electron-main/nativeHelpers.test.ts index c8ffd0c8dbc..199fa7acaf7 100644 --- a/src/vs/code/test/electron-main/nativeHelpers.test.ts +++ b/src/vs/code/test/electron-main/nativeHelpers.test.ts @@ -28,7 +28,9 @@ suite('Windows Native Helpers', () => { }); test('vscode-windows-ca-certs', async () => { - const windowsCerts = await import('vscode-windows-ca-certs'); + const windowsCerts = await new Promise((resolve, reject) => { + require(['vscode-windows-ca-certs'], resolve, reject); + }); assert.ok(windowsCerts, 'Unable to load vscode-windows-ca-certs dependency.'); }); From 2c79231817aa9d95bde7f4019ce5dfae4e059124 Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 11 Nov 2019 09:44:57 -0800 Subject: [PATCH 40/49] fix: don't use appendArgument to add switch values (#84320) * fix: don't use appendArgument to add switch values * args - make sure to allow to enabe color correct rendering --- src/main.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main.js b/src/main.js index d5f4c907fe7..7242b0cb156 100644 --- a/src/main.js +++ b/src/main.js @@ -147,13 +147,9 @@ function configureCommandlineSwitchesSync(cliArgs) { if (argvValue === true || argvValue === 'true') { if (argvKey === 'disable-hardware-acceleration') { app.disableHardwareAcceleration(); // needs to be called explicitly - } else if (argvKey === 'disable-color-correct-rendering') { - app.commandLine.appendSwitch('disable-color-correct-rendering'); // needs to be called exactly like this (https://github.com/microsoft/vscode/issues/84154) } else { - app.commandLine.appendArgument(argvKey); + app.commandLine.appendSwitch(argvKey); } - } else { - app.commandLine.appendSwitch(argvKey, argvValue); } }); From 64489f331be04c228745dd1f4f4263811c568454 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Mon, 11 Nov 2019 10:06:15 -0800 Subject: [PATCH 41/49] Webview strict init #78168 --- src/vs/workbench/api/common/extHostWebview.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index 7da97013142..bd3b3b0daf1 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -16,6 +16,7 @@ import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/we import * as vscode from 'vscode'; import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; import { Disposable as VSCodeDisposable } from './extHostTypes'; +import { assertIsDefined } from 'vs/base/common/types'; type IconPath = URI | { light: URI, dark: URI }; @@ -113,7 +114,8 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa readonly _onDidChangeViewStateEmitter = this._register(new Emitter()); public readonly onDidChangeViewState: Event = this._onDidChangeViewStateEmitter.event; - _capabilities: vscode.WebviewEditorCapabilities; + + public _capabilities?: vscode.WebviewEditorCapabilities; constructor( handle: WebviewPanelHandle, @@ -245,7 +247,7 @@ export class ExtHostWebviewEditor extends Disposable implements vscode.WebviewPa } _undoEdits(edits: string[]): void { - this._capabilities.editingCapability?.undoEdits(edits); + assertIsDefined(this._capabilities).editingCapability?.undoEdits(edits); } private assertNotDisposed() { From f4a8c1d587cabef21545fa972ef9f22bb177395b Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Mon, 11 Nov 2019 08:06:58 -0800 Subject: [PATCH 42/49] Fix #84301 --- .../workbench/contrib/url/common/trustedDomainsValidator.ts | 6 +++++- src/vs/workbench/test/contrib/linkProtection.test.ts | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts index 4cce1c3c6c0..f930d68bccf 100644 --- a/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts +++ b/src/vs/workbench/contrib/url/common/trustedDomainsValidator.ts @@ -158,7 +158,11 @@ export function isURLDomainTrusted(url: URI, trustedDomains: string[]) { } if (url.authority === parsedTrustedDomain.authority) { - return pathMatches(url.path, parsedTrustedDomain.path); + if (pathMatches(url.path, parsedTrustedDomain.path)) { + return true; + } else { + continue; + } } if (trustedDomains[i].indexOf('*') !== -1) { diff --git a/src/vs/workbench/test/contrib/linkProtection.test.ts b/src/vs/workbench/test/contrib/linkProtection.test.ts index 43c443119a7..72269c54214 100644 --- a/src/vs/workbench/test/contrib/linkProtection.test.ts +++ b/src/vs/workbench/test/contrib/linkProtection.test.ts @@ -70,5 +70,7 @@ suite('Link protection domain matching', () => { linkNotAllowedByRules('https://a.x.org/bar', ['https://*.x.org/foo']); linkNotAllowedByRules('https://a.b.x.org/bar', ['https://*.x.org/foo']); + + linkAllowedByRules('https://github.com', ['https://github.com/foo/bar', 'https://github.com']); }); }); From bcede909b4fc963cb029114063abfa8fa5d38128 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 7 Nov 2019 13:36:35 -0800 Subject: [PATCH 43/49] Move keyboard feature detection into caniuse. --- src/vs/base/browser/canIUse.ts | 22 +++++++++++++++---- .../clipboard/browser/clipboardService.ts | 4 ++++ .../keybinding/browser/keybindingService.ts | 12 ++++------ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/vs/base/browser/canIUse.ts b/src/vs/base/browser/canIUse.ts index 157ce940da5..061d1bfc74f 100644 --- a/src/vs/base/browser/canIUse.ts +++ b/src/vs/base/browser/canIUse.ts @@ -6,6 +6,12 @@ import * as browser from 'vs/base/browser/browser'; import * as platform from 'vs/base/common/platform'; +export const enum KeyboardSupport { + Always, + FullScreen, + None +} + /** * Browser feature we can support in current platform, browser and environment. */ @@ -37,9 +43,17 @@ export const BrowserFeatures = { return true; })() }, - /* - * Full Keyboard Support in Full Screen Mode or Standablone - */ - fullKeyboard: !!(navigator).keyboard || browser.isSafari, + keyboard: (() => { + if (platform.isNative || browser.isStandalone) { + return KeyboardSupport.Always; + } + + if ((navigator).keyboard || browser.isSafari) { + return KeyboardSupport.FullScreen; + } + + return KeyboardSupport.None; + })(), + touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0 }; diff --git a/src/vs/workbench/services/clipboard/browser/clipboardService.ts b/src/vs/workbench/services/clipboard/browser/clipboardService.ts index 1a9272c7603..85a5d9db60c 100644 --- a/src/vs/workbench/services/clipboard/browser/clipboardService.ts +++ b/src/vs/workbench/services/clipboard/browser/clipboardService.ts @@ -27,6 +27,10 @@ export class BrowserClipboardService implements IClipboardService { newTextarea.style.visibility = 'false'; newTextarea.style.height = '1px'; newTextarea.style.width = '1px'; + newTextarea.setAttribute('aria-hidden', 'true'); + newTextarea.style.position = 'absolute'; + newTextarea.style.top = '-1000'; + newTextarea.style.left = '-1000'; document.body.appendChild(newTextarea); newTextarea.value = text; newTextarea.focus(); diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index 1adf85f8f61..a582ca420a0 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -46,7 +46,7 @@ import { isArray } from 'vs/base/common/types'; import { INavigatorWithKeyboard, IKeyboard } from 'vs/workbench/services/keybinding/browser/navigatorKeyboard'; import { ScanCodeUtils, IMMUTABLE_CODE_TO_KEY_CODE } from 'vs/base/common/scanCode'; import { flatten } from 'vs/base/common/arrays'; -import { BrowserFeatures } from 'vs/base/browser/canIUse'; +import { BrowserFeatures, KeyboardSupport } from 'vs/base/browser/canIUse'; interface ContributedKeyBinding { command: string; @@ -241,7 +241,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { this._register(browser.onDidChangeFullscreen(() => { const keyboard: IKeyboard | null = (navigator).keyboard; - if (!BrowserFeatures.fullKeyboard) { + if (BrowserFeatures.keyboard === KeyboardSupport.None) { return; } @@ -352,15 +352,11 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } private _assertBrowserConflicts(kb: Keybinding, commandId: string): boolean { - if (!isWeb) { + if (BrowserFeatures.keyboard === KeyboardSupport.Always) { return false; } - if (browser.isStandalone) { - return false; - } - - if (browser.isFullscreen() && BrowserFeatures.fullKeyboard) { + if (BrowserFeatures.keyboard === KeyboardSupport.FullScreen && browser.isFullscreen()) { return false; } From 06cbe30f69581788032b6ed14e518c0e32952518 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 11 Nov 2019 10:41:58 -0800 Subject: [PATCH 44/49] Fix #83599. Run onHide hook when monaco editor is removed from DOM tree. --- src/vs/editor/browser/controller/textAreaHandler.ts | 4 ++++ src/vs/editor/browser/controller/textAreaInput.ts | 10 +++++++++- src/vs/editor/browser/view/viewImpl.ts | 4 ++++ src/vs/editor/browser/widget/codeEditorWidget.ts | 2 ++ src/vs/workbench/browser/parts/editor/baseEditor.ts | 2 ++ src/vs/workbench/browser/parts/editor/editorControl.ts | 1 + .../browser/suggestEnabledInput/suggestEnabledInput.ts | 4 ++++ .../contrib/preferences/browser/settingsEditor2.ts | 4 ++++ 8 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index ec222be4354..70cd98a030a 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -406,6 +406,10 @@ export class TextAreaHandler extends ViewPart { this._textAreaInput.focusTextArea(); } + public refreshFocusState() { + this._textAreaInput.refreshFocusState(); + } + // --- end view API private _primaryCursorVisibleRange: HorizontalPosition | null = null; diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 6e379a0400f..daecfbc4f68 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -163,7 +163,7 @@ export class TextAreaInput extends Disposable { private _isDoingComposition: boolean; private _nextCommand: ReadFromTextArea; - constructor(host: ITextAreaInputHost, textArea: FastDomNode) { + constructor(host: ITextAreaInputHost, private textArea: FastDomNode) { super(); this._host = host; this._textArea = this._register(new TextAreaWrapper(textArea)); @@ -483,6 +483,14 @@ export class TextAreaInput extends Disposable { return this._hasFocus; } + public refreshFocusState(): void { + if (document.body.contains(this.textArea.domNode) && document.activeElement === this.textArea.domNode) { + this._setHasFocus(true); + } else { + this._setHasFocus(false); + } + } + private _setHasFocus(newHasFocus: boolean): void { if (this._hasFocus === newHasFocus) { // no change diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 15f286d6abe..42ef1f6f542 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -528,6 +528,10 @@ export class View extends ViewEventHandler { return this._textAreaHandler.isFocused(); } + public refreshFocusState() { + this._textAreaHandler.refreshFocusState(); + } + public addContentWidget(widgetData: IContentWidgetData): void { this.contentWidgets.addWidget(widgetData.widget); this.layoutContentWidget(widgetData); diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index f387c1308e8..83e47a48f1d 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -869,9 +869,11 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } public onVisible(): void { + this._modelData?.view.refreshFocusState(); } public onHide(): void { + this._modelData?.view.refreshFocusState(); } public getContribution(id: string): T { diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index a830603ce58..669ee10f294 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -112,6 +112,8 @@ export abstract class BaseEditor extends Panel implements IEditor { this.createEditor(parent); } + onHide() { } + /** * Called to create the editor in the parent HTMLElement. */ diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index 3fe6abaef73..432a8fdc546 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -208,6 +208,7 @@ export class EditorControl extends Disposable { if (controlInstanceContainer) { this.parent.removeChild(controlInstanceContainer); hide(controlInstanceContainer); + this._activeControl.onHide(); } // Indicate to editor control diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index bb6029252bf..19f19686a3d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -247,6 +247,10 @@ export class SuggestEnabledInput extends Widget implements IThemable { } } + public onHide(): void { + this.inputWidget.onHide(); + } + public layout(dimension: Dimension): void { this.inputWidget.layout(dimension); this.placeholderText.style.width = `${dimension.width}px`; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index f8d98b3d05c..36c63be5ea9 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -319,6 +319,10 @@ export class SettingsEditor2 extends BaseEditor { this.focusSearch(); } + onHide(): void { + this.searchWidget.onHide(); + } + focusSettings(): void { // Update ARIA global labels const labelElement = this.settingsAriaExtraLabelsContainer.querySelector('#settings_aria_more_actions_shortcut_label'); From 9b34464c6657b68322efa360f18a58ab6abcdda5 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 11 Nov 2019 10:43:33 -0800 Subject: [PATCH 45/49] remove unused isWeb --- .../workbench/services/keybinding/browser/keybindingService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts index a582ca420a0..3867576370d 100644 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/browser/keybindingService.ts @@ -11,7 +11,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Keybinding, ResolvedKeybinding, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; -import { OS, OperatingSystem, isWeb } from 'vs/base/common/platform'; +import { OS, OperatingSystem } from 'vs/base/common/platform'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; From 8e94fc342b8b19ee57beed046f8c4b7562c6eff8 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Fri, 8 Nov 2019 18:47:14 -0500 Subject: [PATCH 46/49] Fixes uri issue with remote uris w/ querystrings --- extensions/image-preview/src/preview.ts | 5 ++++- src/vs/workbench/contrib/webview/common/resourceLoader.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts index 310043c4d37..7b859877304 100644 --- a/extensions/image-preview/src/preview.ts +++ b/extensions/image-preview/src/preview.ts @@ -229,8 +229,11 @@ class Preview extends Disposable { // Show blank image return encodeURI('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg=='); - default: + // Avoid adding cache busting if there is already a query string + if (resource.query) { + return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString(true)); + } return encodeURI(webviewEditor.webview.asWebviewUri(resource).toString(true) + `?version=${version}`); } } diff --git a/src/vs/workbench/contrib/webview/common/resourceLoader.ts b/src/vs/workbench/contrib/webview/common/resourceLoader.ts index 8bde930f42c..ea28683ef4b 100644 --- a/src/vs/workbench/contrib/webview/common/resourceLoader.ts +++ b/src/vs/workbench/contrib/webview/common/resourceLoader.ts @@ -83,7 +83,10 @@ function normalizeRequestPath(requestUri: URI) { // Modern vscode-resources uris put the scheme of the requested resource as the authority if (requestUri.authority) { - return URI.parse(requestUri.authority + ':' + requestUri.path); + return URI.parse(`${requestUri.authority}:${requestUri.path}`).with({ + query: requestUri.query, + fragment: requestUri.fragment + }); } // Old style vscode-resource uris lose the scheme of the resource which means they are unable to From afc29e93645f263c9b28bd6516d3b47a2bf9a507 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Mon, 11 Nov 2019 15:34:04 -0500 Subject: [PATCH 47/49] Fixes #83513 - adds custom titlebar note --- src/vs/workbench/common/theme.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 188874a1199..81c0439428b 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -593,13 +593,13 @@ export const WINDOW_ACTIVE_BORDER = registerColor('window.activeBorder', { dark: null, light: null, hc: contrastBorder -}, nls.localize('windowActiveBorder', "The color used for the border of the window when it is active. Only supported in the desktop client.")); +}, nls.localize('windowActiveBorder', "The color used for the border of the window when it is active. Only supported in the desktop client when using the custom title bar.")); export const WINDOW_INACTIVE_BORDER = registerColor('window.inactiveBorder', { dark: null, light: null, hc: contrastBorder -}, nls.localize('windowInactiveBorder', "The color used for the border of the window when it is inactive. Only supported in the desktop client.")); +}, nls.localize('windowInactiveBorder', "The color used for the border of the window when it is inactive. Only supported in the desktop client when using the custom title bar.")); /** * Base class for all themable workbench components. From b9a9714d4cc002bafaad7ad2983efec6e81870c2 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Mon, 11 Nov 2019 12:22:36 -0800 Subject: [PATCH 48/49] First cut of OAuth in the AuthTokenService --- build/lib/i18n.resources.json | 4 + .../sharedProcess/sharedProcessMain.ts | 2 +- src/vs/platform/auth/common/auth.ts | 12 +- src/vs/platform/auth/common/authTokenIpc.ts | 7 +- .../auth/electron-browser/authTokenService.ts | 266 ++++++++++++++++++ .../userDataSync/browser/userDataSync.ts | 9 +- .../authToken/browser}/authTokenService.ts | 31 +- .../electron-browser/authTokenService.ts | 27 +- src/vs/workbench/workbench.web.main.ts | 2 +- 9 files changed, 328 insertions(+), 32 deletions(-) create mode 100644 src/vs/platform/auth/electron-browser/authTokenService.ts rename src/vs/{platform/auth/common => workbench/services/authToken/browser}/authTokenService.ts (73%) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 848f89cd993..581d2badaf0 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -202,6 +202,10 @@ "name": "vs/workbench/services/actions", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/authToken", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/bulkEdit", "project": "vscode-workbench" diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 9ddf3c0eca9..1492e7710c1 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -59,7 +59,7 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; import { LoggerService } from 'vs/platform/log/node/loggerService'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { IAuthTokenService } from 'vs/platform/auth/common/auth'; -import { AuthTokenService } from 'vs/platform/auth/common/authTokenService'; +import { AuthTokenService } from 'vs/platform/auth/electron-browser/authTokenService'; import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService'; diff --git a/src/vs/platform/auth/common/auth.ts b/src/vs/platform/auth/common/auth.ts index 7b2961d30b0..99102a0fe93 100644 --- a/src/vs/platform/auth/common/auth.ts +++ b/src/vs/platform/auth/common/auth.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; export const enum AuthTokenStatus { Disabled = 'Disabled', @@ -19,11 +20,10 @@ export interface IAuthTokenService { readonly status: AuthTokenStatus; readonly onDidChangeStatus: Event; + readonly _onDidGetCallback: Emitter; - getToken(): Promise; - updateToken(token: string): Promise; + getToken(): Promise; refreshToken(): Promise; - deleteToken(): Promise; - + login(callbackUri?: URI): Promise; + logout(): Promise; } - diff --git a/src/vs/platform/auth/common/authTokenIpc.ts b/src/vs/platform/auth/common/authTokenIpc.ts index a5c78c4a8dd..99a2111750c 100644 --- a/src/vs/platform/auth/common/authTokenIpc.ts +++ b/src/vs/platform/auth/common/authTokenIpc.ts @@ -22,9 +22,12 @@ export class AuthTokenChannel implements IServerChannel { switch (command) { case '_getInitialStatus': return Promise.resolve(this.service.status); case 'getToken': return this.service.getToken(); - case 'updateToken': return this.service.updateToken(args[0]); + case 'exchangeCodeForToken': + this.service._onDidGetCallback.fire(args); + return Promise.resolve(); case 'refreshToken': return this.service.refreshToken(); - case 'deleteToken': return this.service.deleteToken(); + case 'login': return this.service.login(args); + case 'logout': return this.service.logout(); } throw new Error('Invalid call'); } diff --git a/src/vs/platform/auth/electron-browser/authTokenService.ts b/src/vs/platform/auth/electron-browser/authTokenService.ts new file mode 100644 index 00000000000..9b70de53d72 --- /dev/null +++ b/src/vs/platform/auth/electron-browser/authTokenService.ts @@ -0,0 +1,266 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as crypto from 'crypto'; +import * as https from 'https'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; +import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; +import { shell } from 'electron'; + +const SERVICE_NAME = 'VS Code'; +const ACCOUNT = 'MyAccount'; + +const redirectUrlAAD = 'https://vscode-redirect.azurewebsites.net/'; +const activeDirectoryEndpointUrl = 'https://login.microsoftonline.com/'; +const activeDirectoryResourceId = 'https://management.core.windows.net/'; + +const clientId = 'aebc6443-996d-45c2-90f0-388ff96faa56'; +const tenantId = 'common'; + +function parseQuery(uri: URI) { + return uri.query.split('&').reduce((prev: any, current) => { + const queryString = current.split('='); + prev[queryString[0]] = queryString[1]; + return prev; + }, {}); +} + +function toQuery(obj: any): string { + return Object.keys(obj).map(key => `${key}=${obj[key]}`).join('&'); +} + +function toBase64UrlEncoding(base64string: string) { + return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding +} + +export interface IToken { + expiresIn: string; // How long access token is valid, in seconds + expiresOn: string; // When the access token expires in epoch time + accessToken: string; + refreshToken: string; +} + +export class AuthTokenService extends Disposable implements IAuthTokenService { + _serviceBrand: undefined; + + private _status: AuthTokenStatus = AuthTokenStatus.Disabled; + get status(): AuthTokenStatus { return this._status; } + private _onDidChangeStatus: Emitter = this._register(new Emitter()); + readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + + public readonly _onDidGetCallback: Emitter = this._register(new Emitter()); + readonly onDidGetCallback: Event = this._onDidGetCallback.event; + + private _activeToken: IToken | undefined; + + constructor( + @ICredentialsService private readonly credentialsService: ICredentialsService, + @IProductService productService: IProductService, + @IConfigurationService configurationService: IConfigurationService, + ) { + super(); + if (productService.settingsSyncStoreUrl && configurationService.getValue('configurationSync.enableAuth')) { + this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT).then(storedRefreshToken => { + if (storedRefreshToken) { + this.refresh(storedRefreshToken); + } else { + this._status = AuthTokenStatus.Inactive; + } + }); + } + } + + public async login(callbackUri: URI): Promise { + const nonce = generateUuid(); + const port = (callbackUri.authority.match(/:([0-9]*)$/) || [])[1] || (callbackUri.scheme === 'https' || callbackUri.scheme === 'http' ? 443 : 80); + const state = `${callbackUri.scheme},${port},${encodeURIComponent(nonce)},${encodeURIComponent(callbackUri.query)}`; + const signInUrl = `${activeDirectoryEndpointUrl}${tenantId}/oauth2/authorize`; + + const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64')); + const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64')); + + let uri = URI.parse(signInUrl); + uri = uri.with({ + query: `response_type=code&client_id=${encodeURIComponent(clientId)}&redirect_uri=${redirectUrlAAD}&state=${encodeURIComponent(state)}&resource=${activeDirectoryResourceId}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}` + }); + + await shell.openExternal(uri.toString(true)); + + const timeoutPromise = new Promise((resolve: (value: IToken) => void, reject) => { + const wait = setTimeout(() => { + clearTimeout(wait); + reject('Login timed out.'); + }, 1000 * 60 * 5); + }); + + return Promise.race([this.exchangeCodeForToken(clientId, tenantId, codeVerifier, state), timeoutPromise]).then(token => { + this.setToken(token); + }); + } + + public getToken(): Promise { + if (this.status === AuthTokenStatus.Disabled) { + throw new Error('Not enabled'); + } + return Promise.resolve(this._activeToken?.accessToken); + } + + public async refreshToken(): Promise { + if (this.status === AuthTokenStatus.Disabled) { + throw new Error('Not enabled'); + } + + if (!this._activeToken) { + throw new Error('No token to refresh'); + } + + this.refresh(this._activeToken.refreshToken); + } + + private setToken(token: IToken) { + this._activeToken = token; + this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token.refreshToken); + this.setStatus(AuthTokenStatus.Active); + } + + private async exchangeCodeForToken(clientId: string, tenantId: string, codeVerifier: string, state: string): Promise { + let uriEventListener: IDisposable; + return new Promise((resolve: (value: IToken) => void, reject) => { + uriEventListener = this.onDidGetCallback(async (uri: URI) => { + try { + const query = parseQuery(uri); + const code = query.code; + + if (query.state !== state) { + return; + } + + const postData = toQuery({ + grant_type: 'authorization_code', + code: code, + client_id: clientId, + code_verifier: codeVerifier, + redirect_uri: redirectUrlAAD + }); + + const post = https.request({ + host: 'login.microsoftonline.com', + path: `/${tenantId}/oauth2/token`, + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 200) { + const json = JSON.parse(Buffer.concat(buffer).toString()); + resolve({ + expiresIn: json.access_token, + expiresOn: json.expires_on, + accessToken: json.access_token, + refreshToken: json.refresh_token + }); + } else { + reject(new Error('Bad!')); + } + }); + }); + + post.write(postData); + + post.end(); + post.on('error', err => { + reject(err); + }); + + } catch (e) { + reject(e); + } + }); + }).then(result => { + uriEventListener.dispose(); + return result; + }).catch(err => { + uriEventListener.dispose(); + throw err; + }); + } + + private async refresh(refreshToken: string): Promise { + return new Promise((resolve, reject) => { + const postData = toQuery({ + refresh_token: refreshToken, + client_id: clientId, + grant_type: 'refresh_token', + resource: activeDirectoryResourceId + }); + + const post = https.request({ + host: 'login.microsoftonline.com', + path: `/${tenantId}/oauth2/token`, + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length + } + }, result => { + const buffer: Buffer[] = []; + result.on('data', (chunk: Buffer) => { + buffer.push(chunk); + }); + result.on('end', () => { + if (result.statusCode === 200) { + const json = JSON.parse(Buffer.concat(buffer).toString()); + this.setToken({ + expiresIn: json.access_token, + expiresOn: json.expires_on, + accessToken: json.access_token, + refreshToken: json.refresh_token + }); + resolve(); + } else { + reject(new Error('Bad!')); + } + }); + }); + + post.write(postData); + + post.end(); + post.on('error', err => { + reject(err); + }); + }); + } + + async logout(): Promise { + if (this.status === AuthTokenStatus.Disabled) { + throw new Error('Not enabled'); + } + await this.credentialsService.deletePassword(SERVICE_NAME, ACCOUNT); + this._activeToken = undefined; + this.setStatus(AuthTokenStatus.Inactive); + } + + private setStatus(status: AuthTokenStatus): void { + if (this._status !== status) { + this._status = status; + this._onDidChangeStatus.fire(status); + } + } + +} + diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 92cb1c09c88..f1a0ded961b 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -25,7 +25,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { isEqual } from 'vs/base/common/resources'; import { IEditorInput } from 'vs/workbench/common/editor'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { timeout } from 'vs/base/common/async'; const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey('authTokenStatus', AuthTokenStatus.Inactive); @@ -51,7 +50,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @ITextFileService private readonly textFileService: ITextFileService, @IHistoryService private readonly historyService: IHistoryService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, - @IQuickInputService private readonly quickInputService: IQuickInputService, ) { super(); this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService); @@ -138,14 +136,11 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } private async signIn(): Promise { - const token = await this.quickInputService.input({ placeHolder: localize('enter token', "Please provide the auth bearer token"), ignoreFocusLost: true, }); - if (token) { - await this.authTokenService.updateToken(token); - } + return this.authTokenService.login(); } private async signOut(): Promise { - await this.authTokenService.deleteToken(); + await this.authTokenService.logout(); } private async continueSync(): Promise { diff --git a/src/vs/platform/auth/common/authTokenService.ts b/src/vs/workbench/services/authToken/browser/authTokenService.ts similarity index 73% rename from src/vs/platform/auth/common/authTokenService.ts rename to src/vs/workbench/services/authToken/browser/authTokenService.ts index 322ecfb2b09..430a11f7bd8 100644 --- a/src/vs/platform/auth/common/authTokenService.ts +++ b/src/vs/workbench/services/authToken/browser/authTokenService.ts @@ -3,12 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { Disposable } from 'vs/base/common/lifecycle'; import { IProductService } from 'vs/platform/product/common/productService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { URI } from 'vs/base/common/uri'; const SERVICE_NAME = 'VS Code'; const ACCOUNT = 'MyAccount'; @@ -21,10 +24,13 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + readonly _onDidGetCallback: Emitter = this._register(new Emitter()); + constructor( @ICredentialsService private readonly credentialsService: ICredentialsService, @IProductService productService: IProductService, @IConfigurationService configurationService: IConfigurationService, + @IQuickInputService private readonly quickInputService: IQuickInputService ) { super(); if (productService.settingsSyncStoreUrl && configurationService.getValue('configurationSync.enableAuth')) { @@ -37,29 +43,35 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { } } - getToken(): Promise { + async getToken(): Promise { if (this.status === AuthTokenStatus.Disabled) { throw new Error('Not enabled'); } - return this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT); + + const token = await this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT); + if (token) { + return token; + } + + return; } - async updateToken(token: string): Promise { - if (this.status === AuthTokenStatus.Disabled) { - throw new Error('Not enabled'); + async login(): Promise { + const token = await this.quickInputService.input({ placeHolder: localize('enter token', "Please provide the auth bearer token"), ignoreFocusLost: true, }); + if (token) { + await this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token); + this.setStatus(AuthTokenStatus.Active); } - await this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token); - this.setStatus(AuthTokenStatus.Active); } async refreshToken(): Promise { if (this.status === AuthTokenStatus.Disabled) { throw new Error('Not enabled'); } - await this.deleteToken(); + await this.logout(); } - async deleteToken(): Promise { + async logout(): Promise { if (this.status === AuthTokenStatus.Disabled) { throw new Error('Not enabled'); } @@ -75,4 +87,3 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { } } - diff --git a/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts b/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts index b5a2b2d0c0a..7a06e375569 100644 --- a/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts +++ b/src/vs/workbench/services/authToken/electron-browser/authTokenService.ts @@ -9,6 +9,8 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth'; +import { IURLService } from 'vs/platform/url/common/url'; +import { URI } from 'vs/base/common/uri'; export class AuthTokenService extends Disposable implements IAuthTokenService { @@ -21,8 +23,11 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { private _onDidChangeStatus: Emitter = this._register(new Emitter()); readonly onDidChangeStatus: Event = this._onDidChangeStatus.event; + readonly _onDidGetCallback: Emitter = this._register(new Emitter()); + constructor( - @ISharedProcessService sharedProcessService: ISharedProcessService + @ISharedProcessService sharedProcessService: ISharedProcessService, + @IURLService private readonly urlService: IURLService ) { super(); this.channel = sharedProcessService.getChannel('authToken'); @@ -30,22 +35,34 @@ export class AuthTokenService extends Disposable implements IAuthTokenService { this.updateStatus(status); this._register(this.channel.listen('onDidChangeStatus')(status => this.updateStatus(status))); }); + + this.urlService.registerHandler(this); + } + + handleURL(uri: URI) { + if (uri.authority === 'vscode.login') { + this.channel.call('exchangeCodeForToken', uri); + return Promise.resolve(true); + } else { + return Promise.resolve(false); + } } getToken(): Promise { return this.channel.call('getToken'); } - updateToken(token: string): Promise { - return this.channel.call('updateToken', [token]); + login(): Promise { + const callbackUri = this.urlService.create({ authority: 'vscode.login ' }); + return this.channel.call('login', callbackUri); } refreshToken(): Promise { return this.channel.call('getToken'); } - deleteToken(): Promise { - return this.channel.call('deleteToken'); + logout(): Promise { + return this.channel.call('logout'); } private async updateStatus(status: AuthTokenStatus): Promise { diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index bed3663cfcb..0e6601077d3 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -64,7 +64,7 @@ import { NoOpTunnelService } from 'vs/platform/remote/common/tunnelService'; import { ILoggerService } from 'vs/platform/log/common/log'; import { FileLoggerService } from 'vs/platform/log/common/fileLogService'; import { IAuthTokenService } from 'vs/platform/auth/common/auth'; -import { AuthTokenService } from 'vs/platform/auth/common/authTokenService'; +import { AuthTokenService } from 'vs/workbench/services/authToken/browser/authTokenService'; import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; From 994ed41f2f8232442439080ba112a3aa20c9bc55 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 11 Nov 2019 22:44:58 +0100 Subject: [PATCH 49/49] Fix #84528 --- .../common/configurationEditingService.ts | 4 +- .../configurationEditingService.test.ts | 37 ++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/services/configuration/common/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts index ead4e246a36..d0113b72bd7 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -436,7 +436,7 @@ export class ConfigurationEditingService { } if (target === EditableConfigurationTarget.WORKSPACE) { - if (!operation.workspaceStandAloneConfigurationKey) { + if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); if (configurationProperties[operation.key].scope === ConfigurationScope.APPLICATION) { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_WORKSPACE_CONFIGURATION_APPLICATION, target, operation); @@ -452,7 +452,7 @@ export class ConfigurationEditingService { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation); } - if (!operation.workspaceStandAloneConfigurationKey) { + if (!operation.workspaceStandAloneConfigurationKey && !OVERRIDE_PROPERTY_PATTERN.test(operation.key)) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); if (configurationProperties[operation.key].scope !== ConfigurationScope.RESOURCE) { return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_CONFIGURATION, target, operation); diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index 7ad581196cc..d315118d91c 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -18,7 +18,7 @@ import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { ConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode, EditableConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditingService'; -import { WORKSPACE_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; +import { WORKSPACE_STANDALONE_CONFIGURATIONS, FOLDER_SETTINGS_PATH } from 'vs/workbench/services/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -236,6 +236,41 @@ suite('ConfigurationEditingService', () => { }); }); + test('write overridable settings to user settings', () => { + const key = '[language]'; + const value = { 'configurationEditing.service.testSetting': 'overridden value' }; + return testObject.writeConfiguration(EditableConfigurationTarget.USER_LOCAL, { key, value }) + .then(() => { + const contents = fs.readFileSync(globalSettingsFile).toString('utf8'); + const parsed = json.parse(contents); + assert.deepEqual(parsed[key], value); + }); + }); + + test('write overridable settings to workspace settings', () => { + const key = '[language]'; + const value = { 'configurationEditing.service.testSetting': 'overridden value' }; + return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key, value }) + .then(() => { + const target = path.join(workspaceDir, FOLDER_SETTINGS_PATH); + const contents = fs.readFileSync(target).toString('utf8'); + const parsed = json.parse(contents); + assert.deepEqual(parsed[key], value); + }); + }); + + test('write overridable settings to workspace folder settings', () => { + const key = '[language]'; + const value = { 'configurationEditing.service.testSetting': 'overridden value' }; + const folderSettingsFile = path.join(workspaceDir, FOLDER_SETTINGS_PATH); + return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE_FOLDER, { key, value }, { scopes: { resource: URI.file(folderSettingsFile) } }) + .then(() => { + const contents = fs.readFileSync(folderSettingsFile).toString('utf8'); + const parsed = json.parse(contents); + assert.deepEqual(parsed[key], value); + }); + }); + test('write workspace standalone setting - empty file', () => { return testObject.writeConfiguration(EditableConfigurationTarget.WORKSPACE, { key: 'tasks.service.testSetting', value: 'value' }) .then(() => {