From aadfc43a87f7a41398eca8930f218a61d279d9d2 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Wed, 13 Feb 2019 23:20:26 +0100 Subject: [PATCH] move workbench/themes to browser namespace --- package.json | 2 +- src/typings/fast-plist.d.ts | 7 - .../workbench/electron-browser/workbench.ts | 2 +- .../colorThemeData.ts | 35 +- .../colorThemeStore.ts | 2 +- .../themeCompatibility.ts | 10 +- .../workbenchThemeService.ts | 2 +- .../services/themes/common/plistParser.ts | 497 ++++++++++++++++++ 8 files changed, 518 insertions(+), 39 deletions(-) delete mode 100644 src/typings/fast-plist.d.ts rename src/vs/workbench/services/themes/{electron-browser => browser}/colorThemeData.ts (93%) rename src/vs/workbench/services/themes/{electron-browser => browser}/colorThemeStore.ts (98%) rename src/vs/workbench/services/themes/{electron-browser => browser}/themeCompatibility.ts (86%) rename src/vs/workbench/services/themes/{electron-browser => browser}/workbenchThemeService.ts (99%) create mode 100644 src/vs/workbench/services/themes/common/plistParser.ts diff --git a/package.json b/package.json index d1569d3f72c..00b54329e5c 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ }, "dependencies": { "applicationinsights": "1.0.8", - "fast-plist": "0.1.2", "gc-signals": "^0.0.1", "getmac": "1.4.1", "graceful-fs": "4.1.11", @@ -82,6 +81,7 @@ "event-stream": "3.3.4", "express": "^4.13.1", "fancy-log": "^1.3.3", + "fast-plist": "0.1.2", "glob": "^5.0.13", "gulp": "^4.0.0", "gulp-atom-electron": "^1.20.0", diff --git a/src/typings/fast-plist.d.ts b/src/typings/fast-plist.d.ts deleted file mode 100644 index 537e7c2e8ec..00000000000 --- a/src/typings/fast-plist.d.ts +++ /dev/null @@ -1,7 +0,0 @@ - -declare module "fast-plist" { - /** - * A very fast plist parser - */ - export function parse(content: string): any; -} diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index b072bc75f83..e4f96e99365 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -175,7 +175,7 @@ import { LifecycleService } from 'vs/platform/lifecycle/electron-browser/lifecyc import { ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions'; import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver'; import { IExtensionUrlHandler, ExtensionUrlHandler } from 'vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler'; -import { WorkbenchThemeService } from 'vs/workbench/services/themes/electron-browser/workbenchThemeService'; +import { WorkbenchThemeService } from 'vs/workbench/services/themes/browser/workbenchThemeService'; import { DialogService, FileDialogService } from 'vs/workbench/services/dialogs/electron-browser/dialogService'; import { ShowPreviousWindowTab, MoveWindowTabToNewWindow, MergeAllWindowTabs, ShowNextWindowTab, ToggleWindowTabsBar, NewWindowTab, OpenRecentAction, ReloadWindowAction, ReloadWindowWithExtensionsDisabledAction } from 'vs/workbench/electron-browser/actions/windowActions'; import { IBroadcastService, BroadcastService } from 'vs/workbench/services/broadcast/electron-browser/broadcastService'; diff --git a/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts b/src/vs/workbench/services/themes/browser/colorThemeData.ts similarity index 93% rename from src/vs/workbench/services/themes/electron-browser/colorThemeData.ts rename to src/vs/workbench/services/themes/browser/colorThemeData.ts index ce532cb064d..4aaa7b918f2 100644 --- a/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts +++ b/src/vs/workbench/services/themes/browser/colorThemeData.ts @@ -8,7 +8,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 } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { convertSettings } from 'vs/workbench/services/themes/electron-browser/themeCompatibility'; +import { convertSettings } from 'vs/workbench/services/themes/browser/themeCompatibility'; import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; @@ -16,10 +16,11 @@ import * as resources from 'vs/base/common/resources'; import { Extensions, 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 { IColorCustomizations } from 'vs/workbench/services/themes/electron-browser/workbenchThemeService'; +import { IColorCustomizations } from 'vs/workbench/services/themes/browser/workbenchThemeService'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; 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'; let colorRegistry = Registry.as(Extensions.ColorContribution); @@ -330,29 +331,19 @@ function _loadColorTheme(fileService: IFileService, themeLocation: URI, resultRu } } -let pListParser: Promise<{ parse(content: string) }>; -function getPListParser() { - if (!pListParser) { - pListParser = import('fast-plist'); - } - return pListParser; -} - function _loadSyntaxTokens(fileService: IFileService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): Promise { return fileService.resolveContent(themeLocation, { encoding: 'utf8' }).then(content => { - return getPListParser().then(parser => { - try { - let contentValue = parser.parse(content.value.toString()); - let settings: ITokenColorizationRule[] = 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); - return Promise.resolve(null); - } catch (e) { - return Promise.reject(new Error(nls.localize('error.cannotparse', "Problems parsing tmTheme file: {0}", e.message))); + try { + let contentValue = parsePList(content.value.toString()); + let settings: ITokenColorizationRule[] = 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); + return Promise.resolve(null); + } catch (e) { + return Promise.reject(new Error(nls.localize('error.cannotparse', "Problems parsing tmTheme file: {0}", e.message))); + } }, error => { return Promise.reject(new Error(nls.localize('error.cannotload', "Problems loading tmTheme file {0}: {1}", themeLocation.toString(), error.message))); }); diff --git a/src/vs/workbench/services/themes/electron-browser/colorThemeStore.ts b/src/vs/workbench/services/themes/browser/colorThemeStore.ts similarity index 98% rename from src/vs/workbench/services/themes/electron-browser/colorThemeStore.ts rename to src/vs/workbench/services/themes/browser/colorThemeStore.ts index 7955ba6b932..960bb54ec3a 100644 --- a/src/vs/workbench/services/themes/electron-browser/colorThemeStore.ts +++ b/src/vs/workbench/services/themes/browser/colorThemeStore.ts @@ -9,7 +9,7 @@ import * as types from 'vs/base/common/types'; import * as resources from 'vs/base/common/resources'; import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ExtensionData, IThemeExtensionPoint, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { ColorThemeData } from 'vs/workbench/services/themes/electron-browser/colorThemeData'; +import { ColorThemeData } from 'vs/workbench/services/themes/browser/colorThemeData'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; diff --git a/src/vs/workbench/services/themes/electron-browser/themeCompatibility.ts b/src/vs/workbench/services/themes/browser/themeCompatibility.ts similarity index 86% rename from src/vs/workbench/services/themes/electron-browser/themeCompatibility.ts rename to src/vs/workbench/services/themes/browser/themeCompatibility.ts index b5863d584de..577b32e4206 100644 --- a/src/vs/workbench/services/themes/electron-browser/themeCompatibility.ts +++ b/src/vs/workbench/services/themes/browser/themeCompatibility.ts @@ -8,8 +8,6 @@ import { Color } from 'vs/base/common/color'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; import * as editorColorRegistry from 'vs/editor/common/view/editorColorRegistry'; -import * as wordHighlighter from 'vs/editor/contrib/wordHighlighter/wordHighlighter'; -import { peekViewEditorMatchHighlight, peekViewResultsMatchHighlight } from 'vs/editor/contrib/referenceSearch/referencesWidget'; const settingToColorIdMapping: { [settingId: string]: string[] } = {}; function addSettingMapping(settingId: string, colorId: string) { @@ -56,11 +54,11 @@ addSettingMapping('selectionHighlightColor', colorRegistry.editorSelectionHighli addSettingMapping('findMatchHighlight', colorRegistry.editorFindMatchHighlight); addSettingMapping('currentFindMatchHighlight', colorRegistry.editorFindMatch); addSettingMapping('hoverHighlight', colorRegistry.editorHoverHighlight); -addSettingMapping('wordHighlight', wordHighlighter.editorWordHighlight); -addSettingMapping('wordHighlightStrong', wordHighlighter.editorWordHighlightStrong); +addSettingMapping('wordHighlight', 'editor.wordHighlightBackground'); // inlined to avoid editor/contrib dependenies +addSettingMapping('wordHighlightStrong', 'editor.wordHighlightStrongBackground'); addSettingMapping('findRangeHighlight', colorRegistry.editorFindRangeHighlight); -addSettingMapping('findMatchHighlight', peekViewResultsMatchHighlight); -addSettingMapping('referenceHighlight', peekViewEditorMatchHighlight); +addSettingMapping('findMatchHighlight', 'peekViewResult.matchHighlightBackground'); +addSettingMapping('referenceHighlight', 'peekViewEditor.matchHighlightBackground'); addSettingMapping('lineHighlight', editorColorRegistry.editorLineHighlight); addSettingMapping('rangeHighlight', editorColorRegistry.editorRangeHighlight); addSettingMapping('caret', editorColorRegistry.editorCursorForeground); diff --git a/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts similarity index 99% rename from src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts rename to src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 36892b90485..6540a94ea80 100644 --- a/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -19,7 +19,7 @@ import { ITheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/pl import { Event, Emitter } from 'vs/base/common/event'; import { registerFileIconThemeSchemas } from 'vs/workbench/services/themes/common/fileIconThemeSchema'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { ColorThemeStore } from 'vs/workbench/services/themes/electron-browser/colorThemeStore'; +import { ColorThemeStore } from 'vs/workbench/services/themes/browser/colorThemeStore'; import { FileIconThemeStore } from 'vs/workbench/services/themes/common/fileIconThemeStore'; import { FileIconThemeData } from 'vs/workbench/services/themes/common/fileIconThemeData'; import { IWindowService } from 'vs/platform/windows/common/windows'; diff --git a/src/vs/workbench/services/themes/common/plistParser.ts b/src/vs/workbench/services/themes/common/plistParser.ts new file mode 100644 index 00000000000..e4478a5ecf6 --- /dev/null +++ b/src/vs/workbench/services/themes/common/plistParser.ts @@ -0,0 +1,497 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const enum ChCode { + BOM = 65279, + + SPACE = 32, + TAB = 9, + CARRIAGE_RETURN = 13, + LINE_FEED = 10, + + SLASH = 47, + + LESS_THAN = 60, + QUESTION_MARK = 63, + EXCLAMATION_MARK = 33, +} + +const enum State { + ROOT_STATE = 0, + DICT_STATE = 1, + ARR_STATE = 2 +} + +export function parseWithLocation(content: string, filename: string, locationKeyName: string): any { + return _parse(content, filename, locationKeyName); +} + +/** + * A very fast plist parser + */ +export function parse(content: string): any { + return _parse(content, null, null); +} + +function _parse(content: string, filename: string | null, locationKeyName: string | null): any { + const len = content.length; + + let pos = 0; + let line = 1; + let char = 0; + + // Skip UTF8 BOM + if (len > 0 && content.charCodeAt(0) === ChCode.BOM) { + pos = 1; + } + + function advancePosBy(by: number): void { + if (locationKeyName === null) { + pos = pos + by; + } else { + while (by > 0) { + let chCode = content.charCodeAt(pos); + if (chCode === ChCode.LINE_FEED) { + pos++; line++; char = 0; + } else { + pos++; char++; + } + by--; + } + } + } + function advancePosTo(to: number): void { + if (locationKeyName === null) { + pos = to; + } else { + advancePosBy(to - pos); + } + } + + function skipWhitespace(): void { + while (pos < len) { + let chCode = content.charCodeAt(pos); + if (chCode !== ChCode.SPACE && chCode !== ChCode.TAB && chCode !== ChCode.CARRIAGE_RETURN && chCode !== ChCode.LINE_FEED) { + break; + } + advancePosBy(1); + } + } + + function advanceIfStartsWith(str: string): boolean { + if (content.substr(pos, str.length) === str) { + advancePosBy(str.length); + return true; + } + return false; + } + + function advanceUntil(str: string): void { + let nextOccurence = content.indexOf(str, pos); + if (nextOccurence !== -1) { + advancePosTo(nextOccurence + str.length); + } else { + // EOF + advancePosTo(len); + } + } + + function captureUntil(str: string): string { + let nextOccurence = content.indexOf(str, pos); + if (nextOccurence !== -1) { + let r = content.substring(pos, nextOccurence); + advancePosTo(nextOccurence + str.length); + return r; + } else { + // EOF + let r = content.substr(pos); + advancePosTo(len); + return r; + } + } + + let state = State.ROOT_STATE; + + let cur: any = null; + let stateStack: State[] = []; + let objStack: any[] = []; + let curKey: string | null = null; + + function pushState(newState: State, newCur: any): void { + stateStack.push(state); + objStack.push(cur); + state = newState; + cur = newCur; + } + + function popState(): void { + if (stateStack.length === 0) { + return fail('illegal state stack'); + } + state = stateStack.pop()!; + cur = objStack.pop(); + } + + function fail(msg: string): void { + throw new Error('Near offset ' + pos + ': ' + msg + ' ~~~' + content.substr(pos, 50) + '~~~'); + } + + const dictState = { + enterDict: function () { + if (curKey === null) { + return fail('missing '); + } + let newDict = {}; + if (locationKeyName !== null) { + newDict[locationKeyName] = { + filename: filename, + line: line, + char: char + }; + } + cur[curKey] = newDict; + curKey = null; + pushState(State.DICT_STATE, newDict); + }, + enterArray: function () { + if (curKey === null) { + return fail('missing '); + } + let newArr: any[] = []; + cur[curKey] = newArr; + curKey = null; + pushState(State.ARR_STATE, newArr); + } + }; + + const arrState = { + enterDict: function () { + let newDict = {}; + if (locationKeyName !== null) { + newDict[locationKeyName] = { + filename: filename, + line: line, + char: char + }; + } + cur.push(newDict); + pushState(State.DICT_STATE, newDict); + }, + enterArray: function () { + let newArr: any[] = []; + cur.push(newArr); + pushState(State.ARR_STATE, newArr); + } + }; + + + function enterDict() { + if (state === State.DICT_STATE) { + dictState.enterDict(); + } else if (state === State.ARR_STATE) { + arrState.enterDict(); + } else { // ROOT_STATE + cur = {}; + if (locationKeyName !== null) { + cur[locationKeyName] = { + filename: filename, + line: line, + char: char + }; + } + pushState(State.DICT_STATE, cur); + } + } + function leaveDict() { + if (state === State.DICT_STATE) { + popState(); + } else if (state === State.ARR_STATE) { + return fail('unexpected '); + } else { // ROOT_STATE + return fail('unexpected '); + } + } + function enterArray() { + if (state === State.DICT_STATE) { + dictState.enterArray(); + } else if (state === State.ARR_STATE) { + arrState.enterArray(); + } else { // ROOT_STATE + cur = []; + pushState(State.ARR_STATE, cur); + } + } + function leaveArray() { + if (state === State.DICT_STATE) { + return fail('unexpected '); + } else if (state === State.ARR_STATE) { + popState(); + } else { // ROOT_STATE + return fail('unexpected '); + } + } + function acceptKey(val: string) { + if (state === State.DICT_STATE) { + if (curKey !== null) { + return fail('too many '); + } + curKey = val; + } else if (state === State.ARR_STATE) { + return fail('unexpected '); + } else { // ROOT_STATE + return fail('unexpected '); + } + } + function acceptString(val: string) { + if (state === State.DICT_STATE) { + if (curKey === null) { + return fail('missing '); + } + cur[curKey] = val; + curKey = null; + } else if (state === State.ARR_STATE) { + cur.push(val); + } else { // ROOT_STATE + cur = val; + } + } + function acceptReal(val: number) { + if (isNaN(val)) { + return fail('cannot parse float'); + } + if (state === State.DICT_STATE) { + if (curKey === null) { + return fail('missing '); + } + cur[curKey] = val; + curKey = null; + } else if (state === State.ARR_STATE) { + cur.push(val); + } else { // ROOT_STATE + cur = val; + } + } + function acceptInteger(val: number) { + if (isNaN(val)) { + return fail('cannot parse integer'); + } + if (state === State.DICT_STATE) { + if (curKey === null) { + return fail('missing '); + } + cur[curKey] = val; + curKey = null; + } else if (state === State.ARR_STATE) { + cur.push(val); + } else { // ROOT_STATE + cur = val; + } + } + function acceptDate(val: Date) { + if (state === State.DICT_STATE) { + if (curKey === null) { + return fail('missing '); + } + cur[curKey] = val; + curKey = null; + } else if (state === State.ARR_STATE) { + cur.push(val); + } else { // ROOT_STATE + cur = val; + } + } + function acceptData(val: string) { + if (state === State.DICT_STATE) { + if (curKey === null) { + return fail('missing '); + } + cur[curKey] = val; + curKey = null; + } else if (state === State.ARR_STATE) { + cur.push(val); + } else { // ROOT_STATE + cur = val; + } + } + function acceptBool(val: boolean) { + if (state === State.DICT_STATE) { + if (curKey === null) { + return fail('missing '); + } + cur[curKey] = val; + curKey = null; + } else if (state === State.ARR_STATE) { + cur.push(val); + } else { // ROOT_STATE + cur = val; + } + } + + function escapeVal(str: string): string { + return str.replace(/&#([0-9]+);/g, function (_: string, m0: string) { + return (String).fromCodePoint(parseInt(m0, 10)); + }).replace(/&#x([0-9a-f]+);/g, function (_: string, m0: string) { + return (String).fromCodePoint(parseInt(m0, 16)); + }).replace(/&|<|>|"|'/g, function (_: string) { + switch (_) { + case '&': return '&'; + case '<': return '<'; + case '>': return '>'; + case '"': return '"'; + case ''': return '\''; + } + return _; + }); + } + + interface IParsedTag { + name: string; + isClosed: boolean; + } + + function parseOpenTag(): IParsedTag { + let r = captureUntil('>'); + let isClosed = false; + if (r.charCodeAt(r.length - 1) === ChCode.SLASH) { + isClosed = true; + r = r.substring(0, r.length - 1); + } + + return { + name: r.trim(), + isClosed: isClosed + }; + } + + function parseTagValue(tag: IParsedTag): string { + if (tag.isClosed) { + return ''; + } + let val = captureUntil(''); + return escapeVal(val); + } + + while (pos < len) { + skipWhitespace(); + if (pos >= len) { + break; + } + + const chCode = content.charCodeAt(pos); + advancePosBy(1); + if (chCode !== ChCode.LESS_THAN) { + return fail('expected <'); + } + + if (pos >= len) { + return fail('unexpected end of input'); + } + + const peekChCode = content.charCodeAt(pos); + + if (peekChCode === ChCode.QUESTION_MARK) { + advancePosBy(1); + advanceUntil('?>'); + continue; + } + + if (peekChCode === ChCode.EXCLAMATION_MARK) { + advancePosBy(1); + + if (advanceIfStartsWith('--')) { + advanceUntil('-->'); + continue; + } + + advanceUntil('>'); + continue; + } + + if (peekChCode === ChCode.SLASH) { + advancePosBy(1); + skipWhitespace(); + + if (advanceIfStartsWith('plist')) { + advanceUntil('>'); + continue; + } + + if (advanceIfStartsWith('dict')) { + advanceUntil('>'); + leaveDict(); + continue; + } + + if (advanceIfStartsWith('array')) { + advanceUntil('>'); + leaveArray(); + continue; + } + + return fail('unexpected closed tag'); + } + + let tag = parseOpenTag(); + + switch (tag.name) { + case 'dict': + enterDict(); + if (tag.isClosed) { + leaveDict(); + } + continue; + + case 'array': + enterArray(); + if (tag.isClosed) { + leaveArray(); + } + continue; + + case 'key': + acceptKey(parseTagValue(tag)); + continue; + + case 'string': + acceptString(parseTagValue(tag)); + continue; + + case 'real': + acceptReal(parseFloat(parseTagValue(tag))); + continue; + + case 'integer': + acceptInteger(parseInt(parseTagValue(tag), 10)); + continue; + + case 'date': + acceptDate(new Date(parseTagValue(tag))); + continue; + + case 'data': + acceptData(parseTagValue(tag)); + continue; + + case 'true': + parseTagValue(tag); + acceptBool(true); + continue; + + case 'false': + parseTagValue(tag); + acceptBool(false); + continue; + } + + if (/^plist/.test(tag.name)) { + continue; + } + + return fail('unexpected opened tag ' + tag.name); + } + + return cur; +} \ No newline at end of file