| import * as assert from 'assert'; |
| import * as path from 'path'; |
| import * as vscode from 'vscode'; |
| |
| import * as semanticHighlighting from '../src/semantic-highlighting'; |
| |
| suite('SemanticHighlighting Tests', () => { |
| test('Parses arrays of textmate themes.', async () => { |
| const themePath = |
| path.join(__dirname, '../../test/assets/includeTheme.jsonc'); |
| const scopeColorRules = |
| await semanticHighlighting.parseThemeFile(themePath); |
| const getScopeRule = (scope: string) => |
| scopeColorRules.find((v) => v.scope === scope); |
| assert.equal(scopeColorRules.length, 3); |
| assert.deepEqual(getScopeRule('a'), {scope : 'a', foreground : '#fff'}); |
| assert.deepEqual(getScopeRule('b'), {scope : 'b', foreground : '#000'}); |
| assert.deepEqual(getScopeRule('c'), {scope : 'c', foreground : '#bcd'}); |
| }); |
| test('Decodes tokens correctly', () => { |
| const testCases: string[] = [ |
| 'AAAAAAABAAA=', 'AAAAAAADAAkAAAAEAAEAAA==', |
| 'AAAAAAADAAkAAAAEAAEAAAAAAAoAAQAA' |
| ]; |
| const expected = [ |
| [ {character : 0, scopeIndex : 0, length : 1} ], |
| [ |
| {character : 0, scopeIndex : 9, length : 3}, |
| {character : 4, scopeIndex : 0, length : 1} |
| ], |
| [ |
| {character : 0, scopeIndex : 9, length : 3}, |
| {character : 4, scopeIndex : 0, length : 1}, |
| {character : 10, scopeIndex : 0, length : 1} |
| ] |
| ]; |
| testCases.forEach( |
| (testCase, i) => assert.deepEqual( |
| semanticHighlighting.decodeTokens(testCase), expected[i])); |
| }); |
| test('ScopeRules overrides for more specific themes', () => { |
| const rules = [ |
| {scope : 'variable.other.css', foreground : '1'}, |
| {scope : 'variable.other', foreground : '2'}, |
| {scope : 'storage', foreground : '3'}, |
| {scope : 'storage.static', foreground : '4'}, |
| {scope : 'storage', foreground : '5'}, |
| {scope : 'variable.other.parameter', foreground : '6'}, |
| ]; |
| const tm = new semanticHighlighting.ThemeRuleMatcher(rules); |
| assert.deepEqual(tm.getBestThemeRule('variable.other.cpp').scope, |
| 'variable.other'); |
| assert.deepEqual(tm.getBestThemeRule('storage.static').scope, |
| 'storage.static'); |
| assert.deepEqual( |
| tm.getBestThemeRule('storage'), |
| rules[2]); // Match the first element if there are duplicates. |
| assert.deepEqual(tm.getBestThemeRule('variable.other.parameter').scope, |
| 'variable.other.parameter'); |
| assert.deepEqual(tm.getBestThemeRule('variable.other.parameter.cpp').scope, |
| 'variable.other.parameter'); |
| }); |
| test('Colorizer groups decorations correctly', async () => { |
| const scopeTable = [ |
| [ 'variable' ], [ 'entity.type.function' ], |
| [ 'entity.type.function.method' ] |
| ]; |
| // Create the scope source ranges the highlightings should be highlighted |
| // at. Assumes the scopes used are the ones in the "scopeTable" variable. |
| const createHighlightingScopeRanges = |
| (highlightingLines: |
| semanticHighlighting.SemanticHighlightingLine[]) => { |
| // Initialize the scope ranges list to the correct size. Otherwise |
| // scopes that don't have any highlightings are missed. |
| let scopeRanges: vscode.Range[][] = scopeTable.map(() => []); |
| highlightingLines.forEach((line) => { |
| line.tokens.forEach((token) => { |
| scopeRanges[token.scopeIndex].push(new vscode.Range( |
| new vscode.Position(line.line, token.character), |
| new vscode.Position(line.line, |
| token.character + token.length))); |
| }); |
| }); |
| return scopeRanges; |
| }; |
| |
| class MockHighlighter extends semanticHighlighting.Highlighter { |
| applicationUriHistory: string[] = []; |
| // Override to make the highlighting calls accessible to the test. Also |
| // makes the test not depend on visible text editors. |
| applyHighlights(fileUri: string) { |
| this.applicationUriHistory.push(fileUri); |
| } |
| // Override to make it accessible from the test. |
| getDecorationRanges(fileUri: string) { |
| return super.getDecorationRanges(fileUri); |
| } |
| // Override to make tests not depend on visible text editors. |
| getVisibleTextEditorUris() { return [ 'file1', 'file2' ]; } |
| } |
| const highlighter = new MockHighlighter(scopeTable); |
| const tm = new semanticHighlighting.ThemeRuleMatcher([ |
| {scope : 'variable', foreground : '1'}, |
| {scope : 'entity.type', foreground : '2'}, |
| ]); |
| // Recolorizes when initialized. |
| highlighter.highlight('file1', []); |
| assert.deepEqual(highlighter.applicationUriHistory, [ 'file1' ]); |
| highlighter.initialize(tm); |
| assert.deepEqual(highlighter.applicationUriHistory, |
| [ 'file1', 'file1', 'file2' ]); |
| // Groups decorations into the scopes used. |
| let highlightingsInLine: semanticHighlighting.SemanticHighlightingLine[] = [ |
| { |
| line : 1, |
| tokens : [ |
| {character : 1, length : 2, scopeIndex : 1}, |
| {character : 10, length : 2, scopeIndex : 2}, |
| ] |
| }, |
| { |
| line : 2, |
| tokens : [ |
| {character : 3, length : 2, scopeIndex : 1}, |
| {character : 6, length : 2, scopeIndex : 1}, |
| {character : 8, length : 2, scopeIndex : 2}, |
| ] |
| }, |
| ]; |
| |
| highlighter.highlight('file1', highlightingsInLine); |
| assert.deepEqual(highlighter.applicationUriHistory, |
| [ 'file1', 'file1', 'file2', 'file1' ]); |
| assert.deepEqual(highlighter.getDecorationRanges('file1'), |
| createHighlightingScopeRanges(highlightingsInLine)); |
| // Keeps state separate between files. |
| const highlightingsInLine1: |
| semanticHighlighting.SemanticHighlightingLine = { |
| line : 1, |
| tokens : [ |
| {character : 2, length : 1, scopeIndex : 0}, |
| ] |
| }; |
| highlighter.highlight('file2', [ highlightingsInLine1 ]); |
| assert.deepEqual(highlighter.applicationUriHistory, |
| [ 'file1', 'file1', 'file2', 'file1', 'file2' ]); |
| assert.deepEqual(highlighter.getDecorationRanges('file2'), |
| createHighlightingScopeRanges([ highlightingsInLine1 ])); |
| // Does full colorizations. |
| highlighter.highlight('file1', [ highlightingsInLine1 ]); |
| assert.deepEqual(highlighter.applicationUriHistory, |
| [ 'file1', 'file1', 'file2', 'file1', 'file2', 'file1' ]); |
| // After the incremental update to line 1, the old highlightings at line 1 |
| // will no longer exist in the array. |
| assert.deepEqual( |
| highlighter.getDecorationRanges('file1'), |
| createHighlightingScopeRanges( |
| [ highlightingsInLine1, ...highlightingsInLine.slice(1) ])); |
| // Closing a text document removes all highlightings for the file and no |
| // other files. |
| highlighter.removeFileHighlightings('file1'); |
| assert.deepEqual(highlighter.getDecorationRanges('file1'), []); |
| assert.deepEqual(highlighter.getDecorationRanges('file2'), |
| createHighlightingScopeRanges([ highlightingsInLine1 ])); |
| }); |
| }); |