diff --git a/.vscode/launch.json b/.vscode/launch.json index 6cc180e41fcd4..6fa93e1c9c59c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -224,6 +224,21 @@ "order": 10 } }, + { + "type": "node", + "request": "launch", + "name": "HTML Unit Tests", + "program": "${workspaceFolder}/extensions/html-language-features/server/node_modules/mocha/bin/_mocha", + "stopOnEntry": false, + "cwd": "${workspaceFolder}/extensions/html-language-features/server", + "outFiles": [ + "${workspaceFolder}/extensions/html-language-features/server/out/**/*.js" + ], + "presentation": { + "group": "5_tests", + "order": 10 + } + }, { "type": "extensionHost", "request": "launch", diff --git a/extensions/html-language-features/server/src/htmlServerMain.ts b/extensions/html-language-features/server/src/htmlServerMain.ts index 2289c38c95066..7f0045fef14f8 100644 --- a/extensions/html-language-features/server/src/htmlServerMain.ts +++ b/extensions/html-language-features/server/src/htmlServerMain.ts @@ -23,6 +23,7 @@ import { formatError, runSafe, runSafeAsync } from './utils/runner'; import { getFoldingRanges } from './modes/htmlFolding'; import { getDataProviders } from './customData'; import { getSelectionRanges } from './modes/selectionRanges'; +import { SemanticTokenProvider, newSemanticTokenProvider } from './modes/semanticTokens'; namespace TagCloseRequest { export const type: RequestType = new RequestType('html/tag'); @@ -530,14 +531,19 @@ connection.onRequest(MatchingTagPositionRequest.type, (params, token) => { }, null, `Error while computing matching tag position for ${params.textDocument.uri}`, token); }); +let semanticTokensProvider: SemanticTokenProvider | undefined; +function getSemanticTokenProvider() { + if (!semanticTokensProvider) { + semanticTokensProvider = newSemanticTokenProvider(languageModes); + } + return semanticTokensProvider; +} + connection.onRequest(SemanticTokenRequest.type, (params, token) => { return runSafe(() => { const document = documents.get(params.textDocument.uri); if (document) { - const jsMode = languageModes.getMode('javascript'); - if (jsMode && jsMode.getSemanticTokens) { - return jsMode.getSemanticTokens(document, params.ranges); - } + return getSemanticTokenProvider().getSemanticTokens(document, params.ranges); } return null; }, null, `Error while computing semantic tokens for ${params.textDocument.uri}`, token); @@ -545,11 +551,7 @@ connection.onRequest(SemanticTokenRequest.type, (params, token) => { connection.onRequest(SemanticTokenLegendRequest.type, (_params, token) => { return runSafe(() => { - const jsMode = languageModes.getMode('javascript'); - if (jsMode && jsMode.getSemanticTokenLegend) { - return jsMode.getSemanticTokenLegend(); - } - return null; + return getSemanticTokenProvider().legend; }, null, `Error while computing semantic tokens legend`, token); }); diff --git a/extensions/html-language-features/server/src/modes/embeddedSupport.ts b/extensions/html-language-features/server/src/modes/embeddedSupport.ts index 837842c64059e..c3b8ffde437c4 100644 --- a/extensions/html-language-features/server/src/modes/embeddedSupport.ts +++ b/extensions/html-language-features/server/src/modes/embeddedSupport.ts @@ -58,6 +58,8 @@ export function getDocumentRegions(languageService: LanguageService, document: T } else if (lastAttributeName === 'type' && lastTagName.toLowerCase() === 'script') { if (/["'](module|(text|application)\/(java|ecma)script|text\/babel)["']/.test(scanner.getTokenText())) { languageIdFromType = 'javascript'; + } else if (/["']text\/typescript["']/.test(scanner.getTokenText())) { + languageIdFromType = 'typescript'; } else { languageIdFromType = undefined; } diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index d9c1b1623adec..03dc5755c1adc 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -8,7 +8,7 @@ import { SymbolInformation, SymbolKind, CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation, Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, MarkedString, DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions, FoldingRange, FoldingRangeKind, SelectionRange, - LanguageMode, Settings + LanguageMode, Settings, SemanticTokenData } from './languageModes'; import { getWordAtText, startsWith, isWhitespaceOnly, repeat } from '../utils/strings'; import { HTMLDocumentRegions } from './embeddedSupport'; @@ -17,8 +17,6 @@ import * as ts from 'typescript'; import { join } from 'path'; import { getSemanticTokens, getSemanticTokenLegend } from './javascriptSemanticTokens'; -const FILE_NAME = 'vscode://javascript/1'; // the same 'file' is used for all contents -const TS_FILE_NAME = 'vscode://javascript/2.ts'; const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g; let jquery_d_ts = join(__dirname, '../lib/jquery.d.ts'); // when packaged @@ -26,8 +24,10 @@ if (!ts.sys.fileExists(jquery_d_ts)) { jquery_d_ts = join(__dirname, '../../lib/jquery.d.ts'); // from source } -export function getJavaScriptMode(documentRegions: LanguageModelCache): LanguageMode { - let jsDocuments = getLanguageModelCache(10, 60, document => documentRegions.get(document).getEmbeddedDocument('javascript')); +export function getJavaScriptMode(documentRegions: LanguageModelCache, languageId: 'javascript' | 'typescript'): LanguageMode { + let jsDocuments = getLanguageModelCache(10, 60, document => documentRegions.get(document).getEmbeddedDocument(languageId)); + + const workingFile = languageId === 'javascript' ? 'vscode://javascript/1.js' : 'vscode://javascript/2.ts'; // the same 'file' is used for all contents let compilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es6.d.ts'], target: ts.ScriptTarget.Latest, moduleResolution: ts.ModuleResolutionKind.Classic }; let currentTextDocument: TextDocument; @@ -40,10 +40,10 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache compilerOptions, - getScriptFileNames: () => [FILE_NAME, TS_FILE_NAME, jquery_d_ts], - getScriptKind: (fileName) => fileName === TS_FILE_NAME ? ts.ScriptKind.TS : ts.ScriptKind.JS, + getScriptFileNames: () => [workingFile, jquery_d_ts], + getScriptKind: (fileName) => fileName.substr(fileName.length - 2) === 'ts' ? ts.ScriptKind.TS : ts.ScriptKind.JS, getScriptVersion: (fileName: string) => { - if (fileName === FILE_NAME || fileName === TS_FILE_NAME) { + if (fileName === workingFile) { return String(scriptFileVersion); } return '1'; // default lib an jquery.d.ts are static @@ -51,7 +51,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { let text = ''; if (startsWith(fileName, 'vscode:')) { - if (fileName === FILE_NAME || fileName === TS_FILE_NAME) { + if (fileName === workingFile) { text = currentTextDocument.getText(); } } else { @@ -72,17 +72,17 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { return { range: convertRange(currentTextDocument, diag), severity: DiagnosticSeverity.Error, - source: 'js', + source: languageId, message: ts.flattenDiagnosticMessageText(diag.messageText, '\n') }; }); @@ -90,7 +90,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache d.fileName === FILE_NAME).map(d => { + return definition.filter(d => d.fileName === workingFile).map(d => { return { uri: document.uri, range: convertRange(currentTextDocument, d.textSpan) @@ -238,9 +238,9 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache d.fileName === FILE_NAME).map(d => { + return references.filter(d => d.fileName === workingFile).map(d => { return { uri: document.uri, range: convertRange(currentTextDocument, d.textSpan) @@ -255,7 +255,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache d1.offset - d2.offset); - const offsetRanges = ranges.map(r => ({ startOffset: currentTextDocument.offsetAt(r.start), endOffset: currentTextDocument.offsetAt(r.end) })).sort((d1, d2) => d1.startOffset - d2.startOffset); - - let rangeIndex = 0; - let currRange = offsetRanges[rangeIndex++]; - - let prefLine = 0; - let prevChar = 0; - - let encodedResult: number[] = []; - - for (let k = 0; k < resultTokens.length && currRange; k++) { - const curr = resultTokens[k]; - if (currRange.startOffset <= curr.offset && curr.offset + curr.length <= currRange.endOffset) { - // token inside a range - - const startPos = currentTextDocument.positionAt(curr.offset); - if (prefLine !== startPos.line) { - prevChar = 0; - } - encodedResult.push(startPos.line - prefLine); // line delta - encodedResult.push(startPos.character - prevChar); // line delta - encodedResult.push(curr.length); // length - encodedResult.push(curr.typeIdx); // tokenType - encodedResult.push(curr.modifierSet); // tokenModifier - - prefLine = startPos.line; - prevChar = startPos.character; - - } else if (currRange.endOffset >= curr.offset) { - currRange = offsetRanges[rangeIndex++]; - } - } - return encodedResult; + return resultTokens; } @@ -103,15 +61,15 @@ export function getSemanticTokenLegend() { } -const tokenTypes: string[] = ['class', 'enum', 'interface', 'namespace', 'parameterType', 'type', 'parameter', 'variable', 'property', 'constant', 'function', 'member']; +const tokenTypes: string[] = ['class', 'enum', 'interface', 'namespace', 'typeParameter', 'type', 'parameter', 'variable', 'property', 'constant', 'function', 'member']; const tokenModifiers: string[] = ['declaration', 'static', 'async']; -enum TokenType { +const enum TokenType { 'class' = 0, 'enum' = 1, 'interface' = 2, 'namespace' = 3, - 'parameterType' = 4, + 'typeParameter' = 4, 'type' = 5, 'parameter' = 6, 'variable' = 7, @@ -121,22 +79,13 @@ enum TokenType { 'member' = 11 } -enum TokenModifier { + +const enum TokenModifier { 'declaration' = 0x01, 'static' = 0x02, 'async' = 0x04, } -// const tokenFromClassificationMapping: { [name: string]: TokenType } = { -// [ts.ClassificationTypeNames.className]: TokenType.class, -// [ts.ClassificationTypeNames.enumName]: TokenType.enum, -// [ts.ClassificationTypeNames.interfaceName]: TokenType.interface, -// [ts.ClassificationTypeNames.moduleName]: TokenType.namespace, -// [ts.ClassificationTypeNames.typeParameterName]: TokenType.parameterType, -// [ts.ClassificationTypeNames.typeAliasName]: TokenType.type, -// [ts.ClassificationTypeNames.parameterName]: TokenType.parameter -// }; - const tokenFromDeclarationMapping: { [name: string]: TokenType } = { [ts.SyntaxKind.VariableDeclaration]: TokenType.variable, [ts.SyntaxKind.Parameter]: TokenType.parameter, @@ -150,4 +99,7 @@ const tokenFromDeclarationMapping: { [name: string]: TokenType } = { [ts.SyntaxKind.MethodSignature]: TokenType.member, [ts.SyntaxKind.GetAccessor]: TokenType.property, [ts.SyntaxKind.PropertySignature]: TokenType.property, + [ts.SyntaxKind.InterfaceDeclaration]: TokenType.interface, + [ts.SyntaxKind.TypeAliasDeclaration]: TokenType.type, + [ts.SyntaxKind.TypeParameter]: TokenType.typeParameter }; diff --git a/extensions/html-language-features/server/src/modes/languageModes.ts b/extensions/html-language-features/server/src/modes/languageModes.ts index e522de5cb86ab..43bbc1f3d6c0b 100644 --- a/extensions/html-language-features/server/src/modes/languageModes.ts +++ b/extensions/html-language-features/server/src/modes/languageModes.ts @@ -31,6 +31,13 @@ export interface Workspace { readonly folders: WorkspaceFolder[]; } +export interface SemanticTokenData { + start: Position; + length: number; + typeIdx: number; + modifierSet: number; +} + export interface LanguageMode { getId(): string; getSelectionRange?: (document: TextDocument, position: Position) => SelectionRange; @@ -52,7 +59,7 @@ export interface LanguageMode { findMatchingTagPosition?: (document: TextDocument, position: Position) => Position | null; getFoldingRanges?: (document: TextDocument) => FoldingRange[]; onDocumentRemoved(document: TextDocument): void; - getSemanticTokens?(document: TextDocument, ranges: Range[] | undefined): number[]; + getSemanticTokens?(document: TextDocument): SemanticTokenData[]; getSemanticTokenLegend?(): { types: string[], modifiers: string[] }; dispose(): void; } @@ -87,7 +94,8 @@ export function getLanguageModes(supportedLanguages: { [languageId: string]: boo modes['css'] = getCSSMode(cssLanguageService, documentRegions, workspace); } if (supportedLanguages['javascript']) { - modes['javascript'] = getJavaScriptMode(documentRegions); + modes['javascript'] = getJavaScriptMode(documentRegions, 'javascript'); + modes['typescript'] = getJavaScriptMode(documentRegions, 'typescript'); } return { getModeAtPosition(document: TextDocument, position: Position): LanguageMode | undefined { diff --git a/extensions/html-language-features/server/src/modes/selectionRanges.ts b/extensions/html-language-features/server/src/modes/selectionRanges.ts index 6c0627f6356a0..a5d0d0eea8d33 100644 --- a/extensions/html-language-features/server/src/modes/selectionRanges.ts +++ b/extensions/html-language-features/server/src/modes/selectionRanges.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { LanguageModes, TextDocument, Position, Range, SelectionRange } from './languageModes'; +import { insideRangeButNotSame } from '../utils/positions'; export function getSelectionRanges(languageModes: LanguageModes, document: TextDocument, positions: Position[]) { const htmlMode = languageModes.getMode('html'); @@ -23,13 +24,3 @@ export function getSelectionRanges(languageModes: LanguageModes, document: TextD }); } -function beforeOrSame(p1: Position, p2: Position) { - return p1.line < p2.line || p1.line === p2.line && p1.character <= p2.character; -} -function insideRangeButNotSame(r1: Range, r2: Range) { - return beforeOrSame(r1.start, r2.start) && beforeOrSame(r2.end, r1.end) && !equalRange(r1, r2); -} -function equalRange(r1: Range, r2: Range) { - return r1.start.line === r2.start.line && r1.start.character === r2.start.character && r1.end.line === r2.end.line && r1.end.character === r2.end.character; -} - diff --git a/extensions/html-language-features/server/src/modes/semanticTokens.ts b/extensions/html-language-features/server/src/modes/semanticTokens.ts new file mode 100644 index 0000000000000..027c17e5dd9e0 --- /dev/null +++ b/extensions/html-language-features/server/src/modes/semanticTokens.ts @@ -0,0 +1,139 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { SemanticTokenData, Range, TextDocument, LanguageModes, Position } from './languageModes'; +import { beforeOrSame } from '../utils/positions'; + +interface LegendMapping { + types: number[] | undefined; + modifiers: number[] | undefined; +} + +export interface SemanticTokenProvider { + readonly legend: { types: string[]; modifiers: string[] }; + getSemanticTokens(document: TextDocument, ranges?: Range[]): number[]; +} + + +export function newSemanticTokenProvider(languageModes: LanguageModes): SemanticTokenProvider { + + // combined legend across modes + const legend = { types: [], modifiers: [] }; + const legendMappings: { [modeId: string]: LegendMapping } = {}; + + for (let mode of languageModes.getAllModes()) { + if (mode.getSemanticTokenLegend && mode.getSemanticTokens) { + const modeLegend = mode.getSemanticTokenLegend(); + legendMappings[mode.getId()] = { types: createMapping(modeLegend.types, legend.types), modifiers: createMapping(modeLegend.modifiers, legend.modifiers) }; + } + } + + return { + legend, + getSemanticTokens(document: TextDocument, ranges?: Range[]): number[] { + const allTokens: SemanticTokenData[] = []; + for (let mode of languageModes.getAllModesInDocument(document)) { + if (mode.getSemanticTokens) { + const mapping = legendMappings[mode.getId()]; + const tokens = mode.getSemanticTokens(document); + applyTypesMapping(tokens, mapping.types); + applyModifiersMapping(tokens, mapping.modifiers); + for (let token of tokens) { + allTokens.push(token); + } + } + } + return encodeTokens(allTokens, ranges); + } + }; +} + +function createMapping(origLegend: string[], newLegend: string[]): number[] | undefined { + const mapping: number[] = []; + let needsMapping = false; + for (let origIndex = 0; origIndex < origLegend.length; origIndex++) { + const entry = origLegend[origIndex]; + let newIndex = newLegend.indexOf(entry); + if (newIndex === -1) { + newIndex = newLegend.length; + newLegend.push(entry); + } + mapping.push(newIndex); + needsMapping = needsMapping || (newIndex !== origIndex); + } + return needsMapping ? mapping : undefined; +} + +function applyTypesMapping(tokens: SemanticTokenData[], typesMapping: number[] | undefined): void { + if (typesMapping) { + for (let token of tokens) { + token.typeIdx = typesMapping[token.typeIdx]; + } + } +} + +function applyModifiersMapping(tokens: SemanticTokenData[], modifiersMapping: number[] | undefined): void { + if (modifiersMapping) { + for (let token of tokens) { + let modifierSet = token.modifierSet; + if (modifierSet) { + let index = 0; + let result = 0; + while (modifierSet > 0) { + if ((modifierSet & 1) !== 0) { + result = result + (1 << modifiersMapping[index]); + } + index++; + modifierSet = modifierSet >> 1; + } + token.modifierSet = result; + } + } + } +} + +const fullRange = [Range.create(Position.create(0, 0), Position.create(Number.MAX_VALUE, 0))]; + +function encodeTokens(tokens: SemanticTokenData[], ranges?: Range[]): number[] { + + const resultTokens = tokens.sort((d1, d2) => d1.start.line - d2.start.line || d1.start.character - d2.start.character); + if (ranges) { + ranges = ranges.sort((d1, d2) => d1.start.line - d2.start.line || d1.start.character - d2.start.character); + } else { + ranges = fullRange; + } + + let rangeIndex = 0; + let currRange = ranges[rangeIndex++]; + + let prefLine = 0; + let prevChar = 0; + + let encodedResult: number[] = []; + + for (let k = 0; k < resultTokens.length && currRange; k++) { + const curr = resultTokens[k]; + const start = curr.start; + while (currRange && beforeOrSame(currRange.end, start)) { + currRange = ranges[rangeIndex++]; + } + if (currRange && beforeOrSame(currRange.start, start) && beforeOrSame({ line: start.line, character: start.character + curr.length }, currRange.end)) { + // token inside a range + + if (prefLine !== start.line) { + prevChar = 0; + } + encodedResult.push(start.line - prefLine); // line delta + encodedResult.push(start.character - prevChar); // line delta + encodedResult.push(curr.length); // length + encodedResult.push(curr.typeIdx); // tokenType + encodedResult.push(curr.modifierSet); // tokenModifier + + prefLine = start.line; + prevChar = start.character; + } + } + return encodedResult; +} diff --git a/extensions/html-language-features/server/src/test/semanticTokens.test.ts b/extensions/html-language-features/server/src/test/semanticTokens.test.ts index cc5dfaad5c18b..0408b3ccdbf02 100644 --- a/extensions/html-language-features/server/src/test/semanticTokens.test.ts +++ b/extensions/html-language-features/server/src/test/semanticTokens.test.ts @@ -6,6 +6,7 @@ import 'mocha'; import * as assert from 'assert'; import { TextDocument, getLanguageModes, ClientCapabilities, Range, Position } from '../modes/languageModes'; +import { newSemanticTokenProvider } from '../modes/semanticTokens'; interface ExpectedToken { startLine: number; @@ -14,22 +15,17 @@ interface ExpectedToken { tokenClassifiction: string; } -function assertTokens(lines: string[], expected: ExpectedToken[], range?: Range, message?: string): void { +function assertTokens(lines: string[], expected: ExpectedToken[], ranges?: Range[], message?: string): void { const document = TextDocument.create('test://foo/bar.html', 'html', 1, lines.join('\n')); const workspace = { settings: {}, folders: [{ name: 'foo', uri: 'test://foo' }] }; const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST); + const semanticTokensProvider = newSemanticTokenProvider(languageModes); - if (!range) { - range = Range.create(Position.create(0, 0), document.positionAt(document.getText().length)); - } - - const jsMode = languageModes.getMode('javascript')!; - - const legend = jsMode.getSemanticTokenLegend!(); - const actual = jsMode.getSemanticTokens!(document, [range]); + const legend = semanticTokensProvider.legend; + const actual = semanticTokensProvider.getSemanticTokens(document, ranges); let actualRanges = []; let lastLine = 0; @@ -50,9 +46,9 @@ function t(startLine: number, character: number, length: number, tokenClassifict return { startLine, character, length, tokenClassifiction }; } -suite('JavaScript Semantic Tokens', () => { +suite('HTML Semantic Tokens', () => { - test('variables', () => { + test('Variables', () => { const input = [ /*0*/'', /*1*/'', @@ -75,7 +71,7 @@ suite('JavaScript Semantic Tokens', () => { ]); }); - test('function', () => { + test('Functions', () => { const input = [ /*0*/'', /*1*/'', @@ -95,7 +91,7 @@ suite('JavaScript Semantic Tokens', () => { ]); }); - test('members', () => { + test('Members', () => { const input = [ /*0*/'', /*1*/'', @@ -124,6 +120,87 @@ suite('JavaScript Semantic Tokens', () => { ]); }); + test('Interfaces', () => { + const input = [ + /*0*/'', + /*1*/'', + /*2*/'', + /*7*/'', + /*8*/'', + ]; + assertTokens(input, [ + t(3, 12, 8, 'interface.declaration'), t(3, 23, 1, 'property.declaration'), t(3, 34, 1, 'property.declaration'), + t(4, 8, 1, 'variable.declaration'), t(4, 30, 8, 'interface'), + t(5, 8, 3, 'variable.declaration'), t(5, 15, 1, 'parameter.declaration'), t(5, 18, 8, 'interface'), t(5, 31, 1, 'parameter'), t(5, 33, 1, 'property'), t(5, 37, 1, 'parameter'), t(5, 39, 1, 'property') + ]); + }); + + + test('Type aliases and type parameters', () => { + const input = [ + /*0*/'', + /*1*/'', + /*2*/'', + /*8*/'', + /*9*/'', + ]; + assertTokens(input, [ + t(3, 7, 5, 'type.declaration'), t(3, 15, 3, 'variable') /* to investiagte */, + t(4, 11, 1, 'function.declaration'), t(4, 13, 1, 'typeParameter.declaration'), t(4, 23, 5, 'type'), t(4, 30, 1, 'parameter.declaration'), t(4, 33, 1, 'typeParameter'), t(4, 47, 1, 'typeParameter'), + t(5, 12, 1, 'typeParameter'), t(5, 29, 3, 'variable'), t(5, 41, 5, 'type'), + ]); + }); + + test('TS and JS', () => { + const input = [ + /*0*/'', + /*1*/'', + /*2*/'', + /*5*/'', + /*8*/'', + /*9*/'', + ]; + assertTokens(input, [ + t(3, 11, 1, 'function.declaration'), t(3, 13, 1, 'typeParameter.declaration'), t(3, 16, 2, 'parameter.declaration'), t(3, 20, 1, 'typeParameter'), t(3, 24, 1, 'typeParameter'), t(3, 39, 2, 'parameter'), + t(6, 2, 6, 'variable'), t(6, 9, 5, 'member') + ]); + }); + + test('Ranges', () => { + const input = [ + /*0*/'', + /*1*/'', + /*2*/'', + /*5*/'', + /*8*/'', + /*9*/'', + ]; + assertTokens(input, [ + t(3, 2, 6, 'variable'), t(3, 9, 5, 'member') + ], [Range.create(Position.create(2, 0), Position.create(4, 0))]); + + assertTokens(input, [ + t(6, 2, 6, 'variable'), + ], [Range.create(Position.create(6, 2), Position.create(6, 8))]); + }); }); + diff --git a/extensions/html-language-features/server/src/utils/positions.ts b/extensions/html-language-features/server/src/utils/positions.ts new file mode 100644 index 0000000000000..eefd7f61fd42f --- /dev/null +++ b/extensions/html-language-features/server/src/utils/positions.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Position, Range } from '../modes/languageModes'; + +export function beforeOrSame(p1: Position, p2: Position) { + return p1.line < p2.line || p1.line === p2.line && p1.character <= p2.character; +} +export function insideRangeButNotSame(r1: Range, r2: Range) { + return beforeOrSame(r1.start, r2.start) && beforeOrSame(r2.end, r1.end) && !equalRange(r1, r2); +} +export function equalRange(r1: Range, r2: Range) { + return r1.start.line === r2.start.line && r1.start.character === r2.start.character && r1.end.line === r2.end.line && r1.end.character === r2.end.character; +} diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index a67211cf7edd0..1f387335ec2e1 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -378,7 +378,7 @@ function registerDefaultClassifications(): void { registerTokenType('class', nls.localize('class', "Style for classes."), [['entity.name.class']], 'type'); registerTokenType('interface', nls.localize('interface', "Style for interfaces."), undefined, 'type'); registerTokenType('enum', nls.localize('enum', "Style for enums."), undefined, 'type'); - registerTokenType('parameterType', nls.localize('parameterType', "Style for parameter types."), undefined, 'type'); + registerTokenType('typeParameter', nls.localize('typeParameter', "Style for type parameters."), undefined, 'type'); registerTokenType('function', nls.localize('function', "Style for functions"), [['entity.name.function'], ['support.function']]); registerTokenType('member', nls.localize('member', "Style for member"), [['entity.name.function'], ['support.function']]); @@ -395,7 +395,6 @@ function registerDefaultClassifications(): void { tokenClassificationRegistry.registerTokenModifier('declaration', nls.localize('declaration', "Style for all symbol declarations."), undefined); tokenClassificationRegistry.registerTokenModifier('documentation', nls.localize('documentation', "Style to use for references in documentation."), undefined); - //tokenClassificationRegistry.registerTokenModifier('member', nls.localize('member', "Style to use for member functions, variables (fields) and types."), undefined); tokenClassificationRegistry.registerTokenModifier('static', nls.localize('static', "Style to use for symbols that are static."), undefined); tokenClassificationRegistry.registerTokenModifier('abstract', nls.localize('abstract', "Style to use for symbols that are abstract."), undefined); tokenClassificationRegistry.registerTokenModifier('deprecated', nls.localize('deprecated', "Style to use for symbols that are deprecated."), undefined);