diff --git a/packages/shiki/src/__tests__/__mocks__/fs.ts b/packages/shiki/src/__tests__/__mocks__/fs.ts new file mode 100644 index 000000000..8c7dedde3 --- /dev/null +++ b/packages/shiki/src/__tests__/__mocks__/fs.ts @@ -0,0 +1,33 @@ +// __mocks__/fs.ts +import path from 'path' + +const fs = jest.createMockFromModule('fs') + +// This is a custom function that our tests can use during setup to specify +// what the files on the "mock" filesystem should look like when any of the +// `fs` APIs are used. +let mockFiles = Object.create(null) +function __setMockFiles(newMockFiles) { + mockFiles = Object.create(null) + for (const file in newMockFiles) { + const dir = path.dirname(file) + + if (!mockFiles[dir]) { + mockFiles[dir] = [] + } + mockFiles[dir].push(path.basename(file)) + } +} + +// A custom version of `fs.promises.readFile` that reads from the special mocked out +// file list set via __setMockFiles +function promisesReadFile(directoryPath) { + return mockFiles[directoryPath] || [] +} + +fs.__setMockFiles = __setMockFiles +fs.promises = { + readFile: promisesReadFile +} + +module.exports = fs diff --git a/packages/shiki/src/__tests__/fetchtheme.test.ts b/packages/shiki/src/__tests__/fetchtheme.test.ts new file mode 100644 index 000000000..e292cb1d5 --- /dev/null +++ b/packages/shiki/src/__tests__/fetchtheme.test.ts @@ -0,0 +1,1657 @@ +import { parse } from 'jsonc-parser' +import { fetchTheme } from '../loader' + +jest.mock('fs') +jest.mock('jsonc-parser') + +const mockedJsoncParse = parse as jest.MockedFunction + +describe('fetchTheme', () => { + const MOCK_FILE_INFO = { + 'c:\\Users\\fakeUser\\AppData\\Local\\Programs\\Microsoft VS Code\\resources\\app\\extensions\\theme-defaults\\themes\\light_plus.json': + LIGHT_PLUS_MOCK_FILE, + 'c:\\Users\\fakeUser\\AppData\\Local\\Programs\\Microsoft VS Code\\resources\\app\\extensions\\theme-defaults\\themes\\light_vs.json': + LIGHT_VS_MOCK_FILE + } + + beforeEach(() => { + // Set up mocked theme files to be loaded + require('fs').__setMockFiles(MOCK_FILE_INFO) + }) + + test('correctly loads a theme given an absolute directory', async () => { + mockedJsoncParse.mockImplementationOnce(() => Promise.resolve(LIGHT_VS_MOCK_OBJECT)) + + const loadedLightVsShikiTheme = await fetchTheme( + 'c:\\Users\\fakeUser\\AppData\\Local\\Programs\\Microsoft VS Code\\resources\\app\\extensions\\theme-defaults\\themes\\light_vs.json' + ) + + expect(loadedLightVsShikiTheme).toMatchObject(LIGHT_VS_EXPECTED_SHIKI_THEME_OBJECT) + }) + + test('correctly loads a theme in an absolute directory where the def includes another theme using a relative directory', async () => { + mockedJsoncParse + .mockImplementationOnce(() => Promise.resolve(LIGHT_PLUS_MOCK_OBJECT)) + .mockImplementationOnce(() => Promise.resolve(LIGHT_VS_MOCK_OBJECT)) + + const loadedLightPlusShikiTheme = await fetchTheme( + 'c:\\Users\\fakeUser\\AppData\\Local\\Programs\\Microsoft VS Code\\resources\\app\\extensions\\theme-defaults\\themes\\light_plus.json' + ) + + expect(loadedLightPlusShikiTheme).toMatchObject(LIGHT_PLUS_EXPECTED_SHIKI_THEME_OBJECT) + }) +}) + +var LIGHT_PLUS_MOCK_OBJECT = { + $schema: 'vscode://schemas/color-theme', + name: 'Light+ (default light)', + include: './light_vs.json', + tokenColors: [ + // adds rules to the light vs rules + { + name: 'Function declarations', + scope: [ + 'entity.name.function', + 'support.function', + 'support.constant.handlebars', + 'source.powershell variable.other.member', + 'entity.name.operator.custom-literal' // See https://en.cppreference.com/w/cpp/language/user_literal + ], + settings: { + foreground: '#795E26' + } + }, + { + name: 'Types declaration and references', + scope: [ + 'meta.return-type', + 'support.class', + 'support.type', + 'entity.name.type', + 'entity.name.namespace', + 'entity.other.attribute', + 'entity.name.scope-resolution', + 'entity.name.class', + 'storage.type.numeric.go', + 'storage.type.byte.go', + 'storage.type.boolean.go', + 'storage.type.string.go', + 'storage.type.uintptr.go', + 'storage.type.error.go', + 'storage.type.rune.go', + 'storage.type.cs', + 'storage.type.generic.cs', + 'storage.type.modifier.cs', + 'storage.type.variable.cs', + 'storage.type.annotation.java', + 'storage.type.generic.java', + 'storage.type.java', + 'storage.type.object.array.java', + 'storage.type.primitive.array.java', + 'storage.type.primitive.java', + 'storage.type.token.java', + 'storage.type.groovy', + 'storage.type.annotation.groovy', + 'storage.type.parameters.groovy', + 'storage.type.generic.groovy', + 'storage.type.object.array.groovy', + 'storage.type.primitive.array.groovy', + 'storage.type.primitive.groovy' + ], + settings: { + foreground: '#267f99' + } + }, + { + name: 'Types declaration and references, TS grammar specific', + scope: [ + 'meta.type.cast.expr', + 'meta.type.new.expr', + 'support.constant.math', + 'support.constant.dom', + 'support.constant.json', + 'entity.other.inherited-class' + ], + settings: { + foreground: '#267f99' + } + }, + { + name: 'Control flow / Special keywords', + scope: [ + 'keyword.control', + 'source.cpp keyword.operator.new', + 'source.cpp keyword.operator.delete', + 'keyword.other.using', + 'keyword.other.operator', + 'entity.name.operator' + ], + settings: { + foreground: '#AF00DB' + } + }, + { + name: 'Variable and parameter name', + scope: [ + 'variable', + 'meta.definition.variable.name', + 'support.variable', + 'entity.name.variable', + 'constant.other.placeholder' // placeholders in strings + ], + settings: { + foreground: '#001080' + } + }, + { + name: 'Constants and enums', + scope: ['variable.other.constant', 'variable.other.enummember'], + settings: { + foreground: '#0070C1' + } + }, + { + name: 'Object keys, TS grammar specific', + scope: ['meta.object-literal.key'], + settings: { + foreground: '#001080' + } + }, + { + name: 'CSS property value', + scope: [ + 'support.constant.property-value', + 'support.constant.font-name', + 'support.constant.media-type', + 'support.constant.media', + 'constant.other.color.rgb-value', + 'constant.other.rgb-value', + 'support.constant.color' + ], + settings: { + foreground: '#0451a5' + } + }, + { + name: 'Regular expression groups', + scope: [ + 'punctuation.definition.group.regexp', + 'punctuation.definition.group.assertion.regexp', + 'punctuation.definition.character-class.regexp', + 'punctuation.character.set.begin.regexp', + 'punctuation.character.set.end.regexp', + 'keyword.operator.negation.regexp', + 'support.other.parenthesis.regexp' + ], + settings: { + foreground: '#d16969' + } + }, + { + scope: [ + 'constant.character.character-class.regexp', + 'constant.other.character-class.set.regexp', + 'constant.other.character-class.regexp', + 'constant.character.set.regexp' + ], + settings: { + foreground: '#811f3f' + } + }, + { + scope: 'keyword.operator.quantifier.regexp', + settings: { + foreground: '#000000' + } + }, + { + scope: ['keyword.operator.or.regexp', 'keyword.control.anchor.regexp'], + settings: { + foreground: '#EE0000' + } + }, + { + scope: 'constant.character', + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'constant.character.escape', + settings: { + foreground: '#EE0000' + } + }, + { + scope: 'entity.name.label', + settings: { + foreground: '#000000' + } + } + ], + semanticHighlighting: true, + semanticTokenColors: { + newOperator: '#AF00DB', + stringLiteral: '#a31515', + customLiteral: '#795E26', + numberLiteral: '#098658' + } +} +var LIGHT_PLUS_MOCK_FILE = `${LIGHT_PLUS_MOCK_OBJECT}` + +var LIGHT_VS_MOCK_OBJECT: object = { + $schema: 'vscode://schemas/color-theme', + name: 'Light (Visual Studio)', + colors: { + 'editor.background': '#FFFFFF', + 'editor.foreground': '#000000', + 'editor.inactiveSelectionBackground': '#E5EBF1', + 'editorIndentGuide.background': '#D3D3D3', + 'editorIndentGuide.activeBackground': '#939393', + 'editor.selectionHighlightBackground': '#ADD6FF80', + 'editorSuggestWidget.background': '#F3F3F3', + 'activityBarBadge.background': '#007ACC', + 'sideBarTitle.foreground': '#6F6F6F', + 'list.hoverBackground': '#E8E8E8', + 'input.placeholderForeground': '#767676', + 'searchEditor.textInputBorder': '#CECECE', + 'settings.textInputBorder': '#CECECE', + 'settings.numberInputBorder': '#CECECE', + 'statusBarItem.remoteForeground': '#FFF', + 'statusBarItem.remoteBackground': '#16825D', + 'ports.iconRunningProcessForeground': '#369432', + 'sideBarSectionHeader.background': '#0000', + 'sideBarSectionHeader.border': '#61616130', + 'tab.lastPinnedBorder': '#61616130', + 'notebook.cellBorderColor': '#E8E8E8', + 'notebook.selectedCellBackground': '#c8ddf150', + 'statusBarItem.errorBackground': '#c72e0f', + 'list.focusHighlightForeground': '#9DDDFF', + 'list.activeSelectionIconForeground': '#FFF' + }, + tokenColors: [ + { + scope: ['meta.embedded', 'source.groovy.embedded'], + settings: { + foreground: '#000000ff' + } + }, + { + scope: 'emphasis', + settings: { + fontStyle: 'italic' + } + }, + { + scope: 'strong', + settings: { + fontStyle: 'bold' + } + }, + { + scope: 'meta.diff.header', + settings: { + foreground: '#000080' + } + }, + { + scope: 'comment', + settings: { + foreground: '#008000' + } + }, + { + scope: 'constant.language', + settings: { + foreground: '#0000ff' + } + }, + { + scope: [ + 'constant.numeric', + 'variable.other.enummember', + 'keyword.operator.plus.exponent', + 'keyword.operator.minus.exponent' + ], + settings: { + foreground: '#098658' + } + }, + { + scope: 'constant.regexp', + settings: { + foreground: '#811f3f' + } + }, + { + name: 'css tags in selectors, xml tags', + scope: 'entity.name.tag', + settings: { + foreground: '#800000' + } + }, + { + scope: 'entity.name.selector', + settings: { + foreground: '#800000' + } + }, + { + scope: 'entity.other.attribute-name', + settings: { + foreground: '#ff0000' + } + }, + { + scope: [ + 'entity.other.attribute-name.class.css', + 'entity.other.attribute-name.class.mixin.css', + 'entity.other.attribute-name.id.css', + 'entity.other.attribute-name.parent-selector.css', + 'entity.other.attribute-name.pseudo-class.css', + 'entity.other.attribute-name.pseudo-element.css', + 'source.css.less entity.other.attribute-name.id', + 'entity.other.attribute-name.scss' + ], + settings: { + foreground: '#800000' + } + }, + { + scope: 'invalid', + settings: { + foreground: '#cd3131' + } + }, + { + scope: 'markup.underline', + settings: { + fontStyle: 'underline' + } + }, + { + scope: 'markup.bold', + settings: { + fontStyle: 'bold', + foreground: '#000080' + } + }, + { + scope: 'markup.heading', + settings: { + fontStyle: 'bold', + foreground: '#800000' + } + }, + { + scope: 'markup.italic', + settings: { + fontStyle: 'italic' + } + }, + { + scope: 'markup.inserted', + settings: { + foreground: '#098658' + } + }, + { + scope: 'markup.deleted', + settings: { + foreground: '#a31515' + } + }, + { + scope: 'markup.changed', + settings: { + foreground: '#0451a5' + } + }, + { + scope: [ + 'punctuation.definition.quote.begin.markdown', + 'punctuation.definition.list.begin.markdown' + ], + settings: { + foreground: '#0451a5' + } + }, + { + scope: 'markup.inline.raw', + settings: { + foreground: '#800000' + } + }, + { + name: 'brackets of XML/HTML tags', + scope: 'punctuation.definition.tag', + settings: { + foreground: '#800000' + } + }, + { + scope: ['meta.preprocessor', 'entity.name.function.preprocessor'], + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'meta.preprocessor.string', + settings: { + foreground: '#a31515' + } + }, + { + scope: 'meta.preprocessor.numeric', + settings: { + foreground: '#098658' + } + }, + { + scope: 'meta.structure.dictionary.key.python', + settings: { + foreground: '#0451a5' + } + }, + { + scope: 'storage', + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'storage.type', + settings: { + foreground: '#0000ff' + } + }, + { + scope: ['storage.modifier', 'keyword.operator.noexcept'], + settings: { + foreground: '#0000ff' + } + }, + { + scope: ['string', 'meta.embedded.assembly'], + settings: { + foreground: '#a31515' + } + }, + { + scope: [ + 'string.comment.buffered.block.pug', + 'string.quoted.pug', + 'string.interpolated.pug', + 'string.unquoted.plain.in.yaml', + 'string.unquoted.plain.out.yaml', + 'string.unquoted.block.yaml', + 'string.quoted.single.yaml', + 'string.quoted.double.xml', + 'string.quoted.single.xml', + 'string.unquoted.cdata.xml', + 'string.quoted.double.html', + 'string.quoted.single.html', + 'string.unquoted.html', + 'string.quoted.single.handlebars', + 'string.quoted.double.handlebars' + ], + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'string.regexp', + settings: { + foreground: '#811f3f' + } + }, + { + name: 'String interpolation', + scope: [ + 'punctuation.definition.template-expression.begin', + 'punctuation.definition.template-expression.end', + 'punctuation.section.embedded' + ], + settings: { + foreground: '#0000ff' + } + }, + { + name: 'Reset JavaScript string interpolation expression', + scope: ['meta.template.expression'], + settings: { + foreground: '#000000' + } + }, + { + scope: [ + 'support.constant.property-value', + 'support.constant.font-name', + 'support.constant.media-type', + 'support.constant.media', + 'constant.other.color.rgb-value', + 'constant.other.rgb-value', + 'support.constant.color' + ], + settings: { + foreground: '#0451a5' + } + }, + { + scope: [ + 'support.type.vendored.property-name', + 'support.type.property-name', + 'variable.css', + 'variable.scss', + 'variable.other.less', + 'source.coffee.embedded' + ], + settings: { + foreground: '#ff0000' + } + }, + { + scope: ['support.type.property-name.json'], + settings: { + foreground: '#0451a5' + } + }, + { + scope: 'keyword', + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'keyword.control', + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'keyword.operator', + settings: { + foreground: '#000000' + } + }, + { + scope: [ + 'keyword.operator.new', + 'keyword.operator.expression', + 'keyword.operator.cast', + 'keyword.operator.sizeof', + 'keyword.operator.alignof', + 'keyword.operator.typeid', + 'keyword.operator.alignas', + 'keyword.operator.instanceof', + 'keyword.operator.logical.python', + 'keyword.operator.wordlike' + ], + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'keyword.other.unit', + settings: { + foreground: '#098658' + } + }, + { + scope: ['punctuation.section.embedded.begin.php', 'punctuation.section.embedded.end.php'], + settings: { + foreground: '#800000' + } + }, + { + scope: 'support.function.git-rebase', + settings: { + foreground: '#0451a5' + } + }, + { + scope: 'constant.sha.git-rebase', + settings: { + foreground: '#098658' + } + }, + { + name: 'coloring of the Java import and package identifiers', + scope: [ + 'storage.modifier.import.java', + 'variable.language.wildcard.java', + 'storage.modifier.package.java' + ], + settings: { + foreground: '#000000' + } + }, + { + name: 'this.self', + scope: 'variable.language', + settings: { + foreground: '#0000ff' + } + } + ], + semanticHighlighting: true, + semanticTokenColors: { + newOperator: '#0000ff', + stringLiteral: '#a31515', + customLiteral: '#000000', + numberLiteral: '#098658' + } +} +var LIGHT_VS_MOCK_FILE = `${LIGHT_VS_MOCK_OBJECT}` + +var LIGHT_VS_EXPECTED_SHIKI_THEME_OBJECT = { + name: 'Light (Visual Studio)', + type: 'dark', + $schema: 'vscode://schemas/color-theme', + colors: { + 'editor.background': '#FFFFFF', + 'editor.foreground': '#000000', + 'editor.inactiveSelectionBackground': '#E5EBF1', + 'editorIndentGuide.background': '#D3D3D3', + 'editorIndentGuide.activeBackground': '#939393', + 'editor.selectionHighlightBackground': '#ADD6FF80', + 'editorSuggestWidget.background': '#F3F3F3', + 'activityBarBadge.background': '#007ACC', + 'sideBarTitle.foreground': '#6F6F6F', + 'list.hoverBackground': '#E8E8E8', + 'input.placeholderForeground': '#767676', + 'searchEditor.textInputBorder': '#CECECE', + 'settings.textInputBorder': '#CECECE', + 'settings.numberInputBorder': '#CECECE', + 'statusBarItem.remoteForeground': '#FFF', + 'statusBarItem.remoteBackground': '#16825D', + 'ports.iconRunningProcessForeground': '#369432', + 'sideBarSectionHeader.background': '#0000', + 'sideBarSectionHeader.border': '#61616130', + 'tab.lastPinnedBorder': '#61616130', + 'notebook.cellBorderColor': '#E8E8E8', + 'notebook.selectedCellBackground': '#c8ddf150', + 'statusBarItem.errorBackground': '#c72e0f', + 'list.focusHighlightForeground': '#9DDDFF', + 'list.activeSelectionIconForeground': '#FFF' + }, + semanticHighlighting: true, + semanticTokenColors: { + newOperator: '#0000ff', + stringLiteral: '#a31515', + customLiteral: '#000000', + numberLiteral: '#098658' + }, + fg: '#000000', + bg: '#FFFFFF', + settings: [ + { + settings: { + foreground: '#000000', + background: '#FFFFFF' + } + }, + { + scope: ['meta.embedded', 'source.groovy.embedded'], + settings: { + foreground: '#000000ff' + } + }, + { + scope: 'emphasis', + settings: { + fontStyle: 'italic' + } + }, + { + scope: 'strong', + settings: { + fontStyle: 'bold' + } + }, + { + scope: 'meta.diff.header', + settings: { + foreground: '#000080' + } + }, + { + scope: 'comment', + settings: { + foreground: '#008000' + } + }, + { + scope: 'constant.language', + settings: { + foreground: '#0000ff' + } + }, + { + scope: [ + 'constant.numeric', + 'variable.other.enummember', + 'keyword.operator.plus.exponent', + 'keyword.operator.minus.exponent' + ], + settings: { + foreground: '#098658' + } + }, + { + scope: 'constant.regexp', + settings: { + foreground: '#811f3f' + } + }, + { + name: 'css tags in selectors, xml tags', + scope: 'entity.name.tag', + settings: { + foreground: '#800000' + } + }, + { + scope: 'entity.name.selector', + settings: { + foreground: '#800000' + } + }, + { + scope: 'entity.other.attribute-name', + settings: { + foreground: '#ff0000' + } + }, + { + scope: [ + 'entity.other.attribute-name.class.css', + 'entity.other.attribute-name.class.mixin.css', + 'entity.other.attribute-name.id.css', + 'entity.other.attribute-name.parent-selector.css', + 'entity.other.attribute-name.pseudo-class.css', + 'entity.other.attribute-name.pseudo-element.css', + 'source.css.less entity.other.attribute-name.id', + 'entity.other.attribute-name.scss' + ], + settings: { + foreground: '#800000' + } + }, + { + scope: 'invalid', + settings: { + foreground: '#cd3131' + } + }, + { + scope: 'markup.underline', + settings: { + fontStyle: 'underline' + } + }, + { + scope: 'markup.bold', + settings: { + fontStyle: 'bold', + foreground: '#000080' + } + }, + { + scope: 'markup.heading', + settings: { + fontStyle: 'bold', + foreground: '#800000' + } + }, + { + scope: 'markup.italic', + settings: { + fontStyle: 'italic' + } + }, + { + scope: 'markup.inserted', + settings: { + foreground: '#098658' + } + }, + { + scope: 'markup.deleted', + settings: { + foreground: '#a31515' + } + }, + { + scope: 'markup.changed', + settings: { + foreground: '#0451a5' + } + }, + { + scope: [ + 'punctuation.definition.quote.begin.markdown', + 'punctuation.definition.list.begin.markdown' + ], + settings: { + foreground: '#0451a5' + } + }, + { + scope: 'markup.inline.raw', + settings: { + foreground: '#800000' + } + }, + { + name: 'brackets of XML/HTML tags', + scope: 'punctuation.definition.tag', + settings: { + foreground: '#800000' + } + }, + { + scope: ['meta.preprocessor', 'entity.name.function.preprocessor'], + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'meta.preprocessor.string', + settings: { + foreground: '#a31515' + } + }, + { + scope: 'meta.preprocessor.numeric', + settings: { + foreground: '#098658' + } + }, + { + scope: 'meta.structure.dictionary.key.python', + settings: { + foreground: '#0451a5' + } + }, + { + scope: 'storage', + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'storage.type', + settings: { + foreground: '#0000ff' + } + }, + { + scope: ['storage.modifier', 'keyword.operator.noexcept'], + settings: { + foreground: '#0000ff' + } + }, + { + scope: ['string', 'meta.embedded.assembly'], + settings: { + foreground: '#a31515' + } + }, + { + scope: [ + 'string.comment.buffered.block.pug', + 'string.quoted.pug', + 'string.interpolated.pug', + 'string.unquoted.plain.in.yaml', + 'string.unquoted.plain.out.yaml', + 'string.unquoted.block.yaml', + 'string.quoted.single.yaml', + 'string.quoted.double.xml', + 'string.quoted.single.xml', + 'string.unquoted.cdata.xml', + 'string.quoted.double.html', + 'string.quoted.single.html', + 'string.unquoted.html', + 'string.quoted.single.handlebars', + 'string.quoted.double.handlebars' + ], + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'string.regexp', + settings: { + foreground: '#811f3f' + } + }, + { + name: 'String interpolation', + scope: [ + 'punctuation.definition.template-expression.begin', + 'punctuation.definition.template-expression.end', + 'punctuation.section.embedded' + ], + settings: { + foreground: '#0000ff' + } + }, + { + name: 'Reset JavaScript string interpolation expression', + scope: ['meta.template.expression'], + settings: { + foreground: '#000000' + } + }, + { + scope: [ + 'support.constant.property-value', + 'support.constant.font-name', + 'support.constant.media-type', + 'support.constant.media', + 'constant.other.color.rgb-value', + 'constant.other.rgb-value', + 'support.constant.color' + ], + settings: { + foreground: '#0451a5' + } + }, + { + scope: [ + 'support.type.vendored.property-name', + 'support.type.property-name', + 'variable.css', + 'variable.scss', + 'variable.other.less', + 'source.coffee.embedded' + ], + settings: { + foreground: '#ff0000' + } + }, + { + scope: ['support.type.property-name.json'], + settings: { + foreground: '#0451a5' + } + }, + { + scope: 'keyword', + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'keyword.control', + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'keyword.operator', + settings: { + foreground: '#000000' + } + }, + { + scope: [ + 'keyword.operator.new', + 'keyword.operator.expression', + 'keyword.operator.cast', + 'keyword.operator.sizeof', + 'keyword.operator.alignof', + 'keyword.operator.typeid', + 'keyword.operator.alignas', + 'keyword.operator.instanceof', + 'keyword.operator.logical.python', + 'keyword.operator.wordlike' + ], + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'keyword.other.unit', + settings: { + foreground: '#098658' + } + }, + { + scope: ['punctuation.section.embedded.begin.php', 'punctuation.section.embedded.end.php'], + settings: { + foreground: '#800000' + } + }, + { + scope: 'support.function.git-rebase', + settings: { + foreground: '#0451a5' + } + }, + { + scope: 'constant.sha.git-rebase', + settings: { + foreground: '#098658' + } + }, + { + name: 'coloring of the Java import and package identifiers', + scope: [ + 'storage.modifier.import.java', + 'variable.language.wildcard.java', + 'storage.modifier.package.java' + ], + settings: { + foreground: '#000000' + } + }, + { + name: 'this.self', + scope: 'variable.language', + settings: { + foreground: '#0000ff' + } + } + ] +} + +var LIGHT_PLUS_EXPECTED_SHIKI_THEME_OBJECT = { + name: 'Light+ (default light)', + type: 'dark', + $schema: 'vscode://schemas/color-theme', + semanticHighlighting: true, + semanticTokenColors: { + newOperator: '#AF00DB', + stringLiteral: '#a31515', + customLiteral: '#795E26', + numberLiteral: '#098658' + }, + fg: '#bbbbbb', + bg: '#1e1e1e', + settings: [ + { + settings: { + foreground: '#000000', + background: '#FFFFFF' + } + }, + { + scope: ['meta.embedded', 'source.groovy.embedded'], + settings: { + foreground: '#000000ff' + } + }, + { + scope: 'emphasis', + settings: { + fontStyle: 'italic' + } + }, + { + scope: 'strong', + settings: { + fontStyle: 'bold' + } + }, + { + scope: 'meta.diff.header', + settings: { + foreground: '#000080' + } + }, + { + scope: 'comment', + settings: { + foreground: '#008000' + } + }, + { + scope: 'constant.language', + settings: { + foreground: '#0000ff' + } + }, + { + scope: [ + 'constant.numeric', + 'variable.other.enummember', + 'keyword.operator.plus.exponent', + 'keyword.operator.minus.exponent' + ], + settings: { + foreground: '#098658' + } + }, + { + scope: 'constant.regexp', + settings: { + foreground: '#811f3f' + } + }, + { + name: 'css tags in selectors, xml tags', + scope: 'entity.name.tag', + settings: { + foreground: '#800000' + } + }, + { + scope: 'entity.name.selector', + settings: { + foreground: '#800000' + } + }, + { + scope: 'entity.other.attribute-name', + settings: { + foreground: '#ff0000' + } + }, + { + scope: [ + 'entity.other.attribute-name.class.css', + 'entity.other.attribute-name.class.mixin.css', + 'entity.other.attribute-name.id.css', + 'entity.other.attribute-name.parent-selector.css', + 'entity.other.attribute-name.pseudo-class.css', + 'entity.other.attribute-name.pseudo-element.css', + 'source.css.less entity.other.attribute-name.id', + 'entity.other.attribute-name.scss' + ], + settings: { + foreground: '#800000' + } + }, + { + scope: 'invalid', + settings: { + foreground: '#cd3131' + } + }, + { + scope: 'markup.underline', + settings: { + fontStyle: 'underline' + } + }, + { + scope: 'markup.bold', + settings: { + fontStyle: 'bold', + foreground: '#000080' + } + }, + { + scope: 'markup.heading', + settings: { + fontStyle: 'bold', + foreground: '#800000' + } + }, + { + scope: 'markup.italic', + settings: { + fontStyle: 'italic' + } + }, + { + scope: 'markup.inserted', + settings: { + foreground: '#098658' + } + }, + { + scope: 'markup.deleted', + settings: { + foreground: '#a31515' + } + }, + { + scope: 'markup.changed', + settings: { + foreground: '#0451a5' + } + }, + { + scope: [ + 'punctuation.definition.quote.begin.markdown', + 'punctuation.definition.list.begin.markdown' + ], + settings: { + foreground: '#0451a5' + } + }, + { + scope: 'markup.inline.raw', + settings: { + foreground: '#800000' + } + }, + { + name: 'brackets of XML/HTML tags', + scope: 'punctuation.definition.tag', + settings: { + foreground: '#800000' + } + }, + { + scope: ['meta.preprocessor', 'entity.name.function.preprocessor'], + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'meta.preprocessor.string', + settings: { + foreground: '#a31515' + } + }, + { + scope: 'meta.preprocessor.numeric', + settings: { + foreground: '#098658' + } + }, + { + scope: 'meta.structure.dictionary.key.python', + settings: { + foreground: '#0451a5' + } + }, + { + scope: 'storage', + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'storage.type', + settings: { + foreground: '#0000ff' + } + }, + { + scope: ['storage.modifier', 'keyword.operator.noexcept'], + settings: { + foreground: '#0000ff' + } + }, + { + scope: ['string', 'meta.embedded.assembly'], + settings: { + foreground: '#a31515' + } + }, + { + scope: [ + 'string.comment.buffered.block.pug', + 'string.quoted.pug', + 'string.interpolated.pug', + 'string.unquoted.plain.in.yaml', + 'string.unquoted.plain.out.yaml', + 'string.unquoted.block.yaml', + 'string.quoted.single.yaml', + 'string.quoted.double.xml', + 'string.quoted.single.xml', + 'string.unquoted.cdata.xml', + 'string.quoted.double.html', + 'string.quoted.single.html', + 'string.unquoted.html', + 'string.quoted.single.handlebars', + 'string.quoted.double.handlebars' + ], + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'string.regexp', + settings: { + foreground: '#811f3f' + } + }, + { + name: 'String interpolation', + scope: [ + 'punctuation.definition.template-expression.begin', + 'punctuation.definition.template-expression.end', + 'punctuation.section.embedded' + ], + settings: { + foreground: '#0000ff' + } + }, + { + name: 'Reset JavaScript string interpolation expression', + scope: ['meta.template.expression'], + settings: { + foreground: '#000000' + } + }, + { + scope: [ + 'support.constant.property-value', + 'support.constant.font-name', + 'support.constant.media-type', + 'support.constant.media', + 'constant.other.color.rgb-value', + 'constant.other.rgb-value', + 'support.constant.color' + ], + settings: { + foreground: '#0451a5' + } + }, + { + scope: [ + 'support.type.vendored.property-name', + 'support.type.property-name', + 'variable.css', + 'variable.scss', + 'variable.other.less', + 'source.coffee.embedded' + ], + settings: { + foreground: '#ff0000' + } + }, + { + scope: ['support.type.property-name.json'], + settings: { + foreground: '#0451a5' + } + }, + { + scope: 'keyword', + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'keyword.control', + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'keyword.operator', + settings: { + foreground: '#000000' + } + }, + { + scope: [ + 'keyword.operator.new', + 'keyword.operator.expression', + 'keyword.operator.cast', + 'keyword.operator.sizeof', + 'keyword.operator.alignof', + 'keyword.operator.typeid', + 'keyword.operator.alignas', + 'keyword.operator.instanceof', + 'keyword.operator.logical.python', + 'keyword.operator.wordlike' + ], + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'keyword.other.unit', + settings: { + foreground: '#098658' + } + }, + { + scope: ['punctuation.section.embedded.begin.php', 'punctuation.section.embedded.end.php'], + settings: { + foreground: '#800000' + } + }, + { + scope: 'support.function.git-rebase', + settings: { + foreground: '#0451a5' + } + }, + { + scope: 'constant.sha.git-rebase', + settings: { + foreground: '#098658' + } + }, + { + name: 'coloring of the Java import and package identifiers', + scope: [ + 'storage.modifier.import.java', + 'variable.language.wildcard.java', + 'storage.modifier.package.java' + ], + settings: { + foreground: '#000000' + } + }, + { + name: 'this.self', + scope: 'variable.language', + settings: { + foreground: '#0000ff' + } + }, + { + settings: { + foreground: '#bbbbbb', + background: '#1e1e1e' + } + }, + { + name: 'Function declarations', + scope: [ + 'entity.name.function', + 'support.function', + 'support.constant.handlebars', + 'source.powershell variable.other.member', + 'entity.name.operator.custom-literal' + ], + settings: { + foreground: '#795E26' + } + }, + { + name: 'Types declaration and references', + scope: [ + 'meta.return-type', + 'support.class', + 'support.type', + 'entity.name.type', + 'entity.name.namespace', + 'entity.other.attribute', + 'entity.name.scope-resolution', + 'entity.name.class', + 'storage.type.numeric.go', + 'storage.type.byte.go', + 'storage.type.boolean.go', + 'storage.type.string.go', + 'storage.type.uintptr.go', + 'storage.type.error.go', + 'storage.type.rune.go', + 'storage.type.cs', + 'storage.type.generic.cs', + 'storage.type.modifier.cs', + 'storage.type.variable.cs', + 'storage.type.annotation.java', + 'storage.type.generic.java', + 'storage.type.java', + 'storage.type.object.array.java', + 'storage.type.primitive.array.java', + 'storage.type.primitive.java', + 'storage.type.token.java', + 'storage.type.groovy', + 'storage.type.annotation.groovy', + 'storage.type.parameters.groovy', + 'storage.type.generic.groovy', + 'storage.type.object.array.groovy', + 'storage.type.primitive.array.groovy', + 'storage.type.primitive.groovy' + ], + settings: { + foreground: '#267f99' + } + }, + { + name: 'Types declaration and references, TS grammar specific', + scope: [ + 'meta.type.cast.expr', + 'meta.type.new.expr', + 'support.constant.math', + 'support.constant.dom', + 'support.constant.json', + 'entity.other.inherited-class' + ], + settings: { + foreground: '#267f99' + } + }, + { + name: 'Control flow / Special keywords', + scope: [ + 'keyword.control', + 'source.cpp keyword.operator.new', + 'source.cpp keyword.operator.delete', + 'keyword.other.using', + 'keyword.other.operator', + 'entity.name.operator' + ], + settings: { + foreground: '#AF00DB' + } + }, + { + name: 'Variable and parameter name', + scope: [ + 'variable', + 'meta.definition.variable.name', + 'support.variable', + 'entity.name.variable', + 'constant.other.placeholder' + ], + settings: { + foreground: '#001080' + } + }, + { + name: 'Constants and enums', + scope: ['variable.other.constant', 'variable.other.enummember'], + settings: { + foreground: '#0070C1' + } + }, + { + name: 'Object keys, TS grammar specific', + scope: ['meta.object-literal.key'], + settings: { + foreground: '#001080' + } + }, + { + name: 'CSS property value', + scope: [ + 'support.constant.property-value', + 'support.constant.font-name', + 'support.constant.media-type', + 'support.constant.media', + 'constant.other.color.rgb-value', + 'constant.other.rgb-value', + 'support.constant.color' + ], + settings: { + foreground: '#0451a5' + } + }, + { + name: 'Regular expression groups', + scope: [ + 'punctuation.definition.group.regexp', + 'punctuation.definition.group.assertion.regexp', + 'punctuation.definition.character-class.regexp', + 'punctuation.character.set.begin.regexp', + 'punctuation.character.set.end.regexp', + 'keyword.operator.negation.regexp', + 'support.other.parenthesis.regexp' + ], + settings: { + foreground: '#d16969' + } + }, + { + scope: [ + 'constant.character.character-class.regexp', + 'constant.other.character-class.set.regexp', + 'constant.other.character-class.regexp', + 'constant.character.set.regexp' + ], + settings: { + foreground: '#811f3f' + } + }, + { + scope: 'keyword.operator.quantifier.regexp', + settings: { + foreground: '#000000' + } + }, + { + scope: ['keyword.operator.or.regexp', 'keyword.control.anchor.regexp'], + settings: { + foreground: '#EE0000' + } + }, + { + scope: 'constant.character', + settings: { + foreground: '#0000ff' + } + }, + { + scope: 'constant.character.escape', + settings: { + foreground: '#EE0000' + } + }, + { + scope: 'entity.name.label', + settings: { + foreground: '#000000' + } + } + ], + colors: { + 'editor.background': '#FFFFFF', + 'editor.foreground': '#000000', + 'editor.inactiveSelectionBackground': '#E5EBF1', + 'editorIndentGuide.background': '#D3D3D3', + 'editorIndentGuide.activeBackground': '#939393', + 'editor.selectionHighlightBackground': '#ADD6FF80', + 'editorSuggestWidget.background': '#F3F3F3', + 'activityBarBadge.background': '#007ACC', + 'sideBarTitle.foreground': '#6F6F6F', + 'list.hoverBackground': '#E8E8E8', + 'input.placeholderForeground': '#767676', + 'searchEditor.textInputBorder': '#CECECE', + 'settings.textInputBorder': '#CECECE', + 'settings.numberInputBorder': '#CECECE', + 'statusBarItem.remoteForeground': '#FFF', + 'statusBarItem.remoteBackground': '#16825D', + 'ports.iconRunningProcessForeground': '#369432', + 'sideBarSectionHeader.background': '#0000', + 'sideBarSectionHeader.border': '#61616130', + 'tab.lastPinnedBorder': '#61616130', + 'notebook.cellBorderColor': '#E8E8E8', + 'notebook.selectedCellBackground': '#c8ddf150', + 'statusBarItem.errorBackground': '#c72e0f', + 'list.focusHighlightForeground': '#9DDDFF', + 'list.activeSelectionIconForeground': '#FFF' + } +} diff --git a/packages/shiki/src/__tests__/utils.test.ts b/packages/shiki/src/__tests__/utils.test.ts index 6f71ff751..726f2b38f 100644 --- a/packages/shiki/src/__tests__/utils.test.ts +++ b/packages/shiki/src/__tests__/utils.test.ts @@ -1,4 +1,4 @@ -import { trimEndSlash, join, dirname } from '../utils' +import { trimEndSlash, join, dirname, dirpathparts } from '../utils' test('trimEndSlash', async () => { expect(trimEndSlash('/abc')).toBe('/abc') @@ -22,3 +22,41 @@ test('dirname', async () => { expect(dirname('a')).toBe(undefined) expect(dirname('')).toBe(undefined) }) + +test('dirpathparts', async () => { + expect(dirpathparts('a/b/c')).toStrictEqual(['a', 'b']) + expect(dirpathparts('a/b/c/')).toStrictEqual(['a', 'b', 'c']) + expect(dirpathparts('a/b/c/def.g')).toStrictEqual(['a', 'b', 'c']) + expect(dirpathparts('./a/b/c/')).toStrictEqual(['.', 'a', 'b', 'c']) + expect(dirpathparts('./a/b/c/def.g')).toStrictEqual(['.', 'a', 'b', 'c']) + expect(dirpathparts('/a/b/c/')).toStrictEqual(['', 'a', 'b', 'c']) + expect(dirpathparts('/a/b/c/def.g')).toStrictEqual(['', 'a', 'b', 'c']) + expect(dirpathparts('a\\b\\c')).toStrictEqual(['a', 'b']) + expect(dirpathparts('a\\b\\c\\')).toStrictEqual(['a', 'b', 'c']) + expect(dirpathparts('a\\b\\c\\def.g')).toStrictEqual(['a', 'b', 'c']) + expect(dirpathparts('.\\a\\b\\c\\')).toStrictEqual(['.', 'a', 'b', 'c']) + expect(dirpathparts('\\a\\b\\c\\')).toStrictEqual(['', 'a', 'b', 'c']) + expect(dirpathparts('a')).toStrictEqual([]) + expect(dirpathparts('')).toStrictEqual([]) +}) + +test('dir_join_with_new_file', async () => { + expect(join(...dirpathparts('a/b/c'), 'hij.k')).toBe('a/b/hij.k') + expect(join(...dirpathparts('a/b/c/'), 'hij.k')).toBe('a/b/c/hij.k') + expect(join(...dirpathparts('a/b/c/def.g'), 'hij.k')).toBe('a/b/c/hij.k') + expect(join(...dirpathparts('./a/b/c'), 'hij.k')).toBe('./a/b/hij.k') + expect(join(...dirpathparts('./a/b/c/'), 'hij.k')).toBe('./a/b/c/hij.k') + expect(join(...dirpathparts('./a/b/c/def.g'), 'hij.k')).toBe('./a/b/c/hij.k') + expect(join(...dirpathparts('/a/b/c'), 'hij.k')).toBe('/a/b/hij.k') + expect(join(...dirpathparts('/a/b/c/'), 'hij.k')).toBe('/a/b/c/hij.k') + expect(join(...dirpathparts('/a/b/c/def.g'), 'hij.k')).toBe('/a/b/c/hij.k') + expect(join(...dirpathparts('a\\b\\c'), 'hij.k')).toBe('a/b/hij.k') + expect(join(...dirpathparts('a\\b\\c\\'), 'hij.k')).toBe('a/b/c/hij.k') + expect(join(...dirpathparts('a\\b\\c\\def.g'), 'hij.k')).toBe('a/b/c/hij.k') + expect(join(...dirpathparts('.\\a\\b\\c'), 'hij.k')).toBe('./a/b/hij.k') + expect(join(...dirpathparts('.\\a\\b\\c\\'), 'hij.k')).toBe('./a/b/c/hij.k') + expect(join(...dirpathparts('.\\a\\b\\c\\def.g'), 'hij.k')).toBe('./a/b/c/hij.k') + expect(join(...dirpathparts('\\a\\b\\c'), 'hij.k')).toBe('/a/b/hij.k') + expect(join(...dirpathparts('\\a\\b\\c\\'), 'hij.k')).toBe('/a/b/c/hij.k') + expect(join(...dirpathparts('\\a\\b\\c\\def.g'), 'hij.k')).toBe('/a/b/c/hij.k') +}) diff --git a/packages/shiki/src/loader.ts b/packages/shiki/src/loader.ts index 1818b9ded..67f525d8a 100644 --- a/packages/shiki/src/loader.ts +++ b/packages/shiki/src/loader.ts @@ -1,4 +1,4 @@ -import { join, dirname } from './utils' +import { join, dirpathparts } from './utils' import type { IOnigLib, IRawGrammar, IRawTheme } from 'vscode-textmate' import { loadWASM, createOnigScanner, createOnigString } from 'vscode-oniguruma' import { parse, ParseError } from 'jsonc-parser' @@ -130,7 +130,7 @@ export async function fetchTheme(themePath: string): Promise { const shikiTheme = toShikiTheme(theme) if (shikiTheme.include) { - const includedTheme = await fetchTheme(join(dirname(themePath), shikiTheme.include)) + const includedTheme = await fetchTheme(join(...dirpathparts(themePath), shikiTheme.include)) if (includedTheme.settings) { shikiTheme.settings = includedTheme.settings.concat(shikiTheme.settings) diff --git a/packages/shiki/src/utils.ts b/packages/shiki/src/utils.ts index 84f8e3baa..e0213e789 100644 --- a/packages/shiki/src/utils.ts +++ b/packages/shiki/src/utils.ts @@ -13,6 +13,11 @@ export function dirname(str: string) { return parts[parts.length - 2] } +export function dirpathparts(str: string) { + const parts = str.split(/[\/\\]/g) + return parts.slice(0, parts.length - 1) +} + export function join(...parts: string[]) { return parts.map(trimEndSlash).map(trimStartDot).join('/') }