semantic tokens
This commit is contained in:
parent
6d9caeff57
commit
2798856ce8
|
@ -0,0 +1,248 @@
|
|||
import { SemanticTokens, SemanticTokensBuilder } from "vscode-languageserver";
|
||||
import { URI } from "vscode-uri";
|
||||
import Token from "./parser/antlr4/Token";
|
||||
import CMakeListener from "./parser/CMakeListener";
|
||||
import { initParams, variables } from "./server";
|
||||
import * as builtinCmds from './builtin-cmds.json';
|
||||
|
||||
export let tokenTypes = [
|
||||
'type',
|
||||
'enum',
|
||||
'parameter',
|
||||
'variable',
|
||||
'property',
|
||||
'function',
|
||||
'macro',
|
||||
'keyword',
|
||||
'modifier',
|
||||
'comment',
|
||||
'string',
|
||||
'number',
|
||||
'regexp',
|
||||
'operator'
|
||||
];
|
||||
|
||||
export let tokenModifiers = [
|
||||
'definition',
|
||||
'readonly',
|
||||
'deprecated',
|
||||
'documentation'
|
||||
];
|
||||
|
||||
enum TokenModifiers {
|
||||
definition = 2 ** 0,
|
||||
readonly = 2 ** 1,
|
||||
deprecated = 2 ** 2,
|
||||
documentation = 2 ** 3
|
||||
};
|
||||
|
||||
enum TokenTypes {
|
||||
type = 'type',
|
||||
enum = 'enum',
|
||||
parameter = 'parameter',
|
||||
variable = 'variable',
|
||||
property = 'property',
|
||||
function = 'function',
|
||||
macro = 'macro',
|
||||
keyword = 'keyword',
|
||||
modifier = 'modifier',
|
||||
comment = 'comment',
|
||||
string = 'string',
|
||||
number = 'number',
|
||||
regexp = 'regexp',
|
||||
operator = 'operator'
|
||||
}
|
||||
|
||||
export const tokenBuilders: Map<string, SemanticTokensBuilder> = new Map();
|
||||
|
||||
export function getTokenTypes(): string[] {
|
||||
tokenTypes = tokenTypes.filter((value, index, arr) => {
|
||||
return initParams.capabilities.textDocument.semanticTokens.tokenTypes.includes(value);
|
||||
});
|
||||
return tokenTypes;
|
||||
}
|
||||
|
||||
export function getTokenModifiers(): string[] {
|
||||
tokenModifiers = tokenModifiers.filter((value, index, arr) => {
|
||||
return initParams.capabilities.textDocument.semanticTokens.tokenModifiers.includes(value);
|
||||
});
|
||||
return tokenModifiers;
|
||||
}
|
||||
|
||||
// export function getTokenBuilder(document: TextDocument): SemanticTokensBuilder {
|
||||
// let result = tokenBuilders.get(document.uri);
|
||||
// if (result !== undefined) {
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// result = new SemanticTokensBuilder();
|
||||
// tokenBuilders.set(document.uri, result);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
export class SemanticListener extends CMakeListener {
|
||||
private _data: number[] = [];
|
||||
private _builder: SemanticTokensBuilder;
|
||||
private _uri: URI;
|
||||
|
||||
private _operator: string[] = [
|
||||
'EXISTS', 'COMMAND', 'DEFINED',
|
||||
'EQUAL', 'LESS', 'LESS_EQUAL', 'GREATER', 'GREATER_EAUAL', 'STREQUAL',
|
||||
'STRLESS', 'STRLESS_EQUAL', 'STRGREATER', 'STRGREATER_EQUAL',
|
||||
'VERSION_EQUAL', 'VERSION_LESS', 'VERSION_LESS_EQUAL', 'VERSION_GREATER',
|
||||
'VERSION_GREATER_EQUAL', 'PATH_EQUAL', 'MATCHES',
|
||||
'AND', 'NOT', 'OR'
|
||||
];
|
||||
|
||||
constructor(uri: URI) {
|
||||
super();
|
||||
this._uri = uri;
|
||||
this._builder = this.getTokenBuilder(uri.toString());
|
||||
}
|
||||
|
||||
private isOperator(token: string): boolean {
|
||||
return this._operator.includes(token);
|
||||
}
|
||||
|
||||
private isVariable(token: string): boolean {
|
||||
return variables.includes(token);
|
||||
}
|
||||
|
||||
private getModifiers(modifiers: TokenModifiers[]): number {
|
||||
let result = 0;
|
||||
for (let modifier of modifiers) {
|
||||
result |= modifier;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private getTokenBuilder(uri: string): SemanticTokensBuilder {
|
||||
let result = tokenBuilders.get(uri);
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = new SemanticTokensBuilder();
|
||||
tokenBuilders.set(uri, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private getCmdKeyWords(sigs: string[]): string[] {
|
||||
let result: string[] = [];
|
||||
sigs.forEach(sig => {
|
||||
const keys = sig.match(/[A-Z_]+/g);
|
||||
if (keys !== null) {
|
||||
keys.forEach(key => {
|
||||
result.push(key);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
enterIfCmd(ctx: any): void {
|
||||
ctx.argument().forEach(argCtx => {
|
||||
if (argCtx.getChildCount() === 1) {
|
||||
const argToken: Token = argCtx.start;
|
||||
if (this.isOperator(argToken.text)) {
|
||||
this._builder.push(argToken.line - 1, argToken.column,
|
||||
argToken.text.length, tokenTypes.indexOf(TokenTypes.operator),
|
||||
this.getModifiers([]));
|
||||
} else if (this.isVariable(argToken.text)) {
|
||||
this._builder.push(argToken.line - 1, argToken.column,
|
||||
argToken.text.length, tokenTypes.indexOf(TokenTypes.variable),
|
||||
this.getModifiers([]));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
enterElseIfCmd(ctx: any): void {
|
||||
this.enterIfCmd(ctx);
|
||||
}
|
||||
|
||||
enterForeachCmd(ctx: any): void {
|
||||
|
||||
}
|
||||
|
||||
|
||||
enterWhileCmd(ctx: any): void {
|
||||
this.enterIfCmd(ctx);
|
||||
}
|
||||
|
||||
enterFunctionCmd(ctx: any): void {
|
||||
const argCount = ctx.argument().length;
|
||||
if (argCount > 1) {
|
||||
ctx.argument().slice(1).forEach(argCtx => {
|
||||
if (argCtx.getChildCount() === 1) {
|
||||
const argToken: Token = argCtx.start;
|
||||
this._builder.push(argToken.line - 1, argToken.column, argToken.text.length,
|
||||
tokenTypes.indexOf(TokenTypes.parameter), this.getModifiers([]));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
enterMacroCmd(ctx: any): void {
|
||||
this.enterFunctionCmd(ctx);
|
||||
}
|
||||
|
||||
enterSetCmd(ctx: any): void {
|
||||
const argCount = ctx.argument().length;
|
||||
if (argCount > 0) {
|
||||
const varCtx = ctx.argument(0);
|
||||
const varToken: Token = varCtx.start;
|
||||
this._builder.push(varToken.line - 1, varToken.column, varToken.text.length,
|
||||
tokenTypes.indexOf(TokenTypes.variable),
|
||||
this.getModifiers([TokenModifiers.definition]));
|
||||
}
|
||||
}
|
||||
|
||||
enterOptionCmd(ctx: any): void {
|
||||
|
||||
}
|
||||
|
||||
enterIncludeCmd(ctx: any): void {
|
||||
|
||||
}
|
||||
|
||||
enterAddSubDirCmd(ctx: any): void {
|
||||
|
||||
}
|
||||
|
||||
enterOtherCmd(ctx: any): void {
|
||||
const cmdName: Token = ctx.ID().symbol;
|
||||
if (cmdName.text in builtinCmds) {
|
||||
if ('deprecated' in builtinCmds[cmdName.text]) {
|
||||
this._builder.push(cmdName.line - 1, cmdName.column,
|
||||
cmdName.text.length, tokenTypes.indexOf(TokenTypes.function),
|
||||
this.getModifiers([TokenModifiers.deprecated]));
|
||||
}
|
||||
|
||||
const sigs: string[] = builtinCmds[cmdName.text]['sig'];
|
||||
const keywords = this.getCmdKeyWords(sigs);
|
||||
if (ctx.argument().length > 0) {
|
||||
ctx.argument().forEach(argCtx => {
|
||||
if (argCtx.getChildCount() === 1) {
|
||||
const argToken: Token = argCtx.start;
|
||||
if (keywords.includes(argToken.text)) {
|
||||
this._builder.push(argToken.line - 1, argToken.column,
|
||||
argToken.text.length, tokenTypes.indexOf(TokenTypes.keyword),
|
||||
this.getModifiers([]));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this._builder.push(cmdName.line - 1, cmdName.column,
|
||||
cmdName.text.length, tokenTypes.indexOf(TokenTypes.function),
|
||||
this.getModifiers([]));
|
||||
}
|
||||
}
|
||||
|
||||
public getSemanticTokens(): SemanticTokens {
|
||||
return this._builder.build();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
createConnection, HoverParams, InitializeParams, InitializeResult,
|
||||
ProposedFeatures, SignatureHelpParams, TextDocuments, TextDocumentSyncKind,
|
||||
ProposedFeatures, SemanticTokensParams, SemanticTokensRequest, SignatureHelpParams, TextDocuments, TextDocumentSyncKind,
|
||||
_RemoteWindow
|
||||
} from 'vscode-languageserver/node';
|
||||
|
||||
|
@ -26,6 +26,7 @@ import { DefinationListener, incToBaseDir, refToDef, topScope } from './symbolTa
|
|||
import { existsSync } from 'fs';
|
||||
import { URI, Utils } from 'vscode-uri';
|
||||
import path = require('path');
|
||||
import { getTokenModifiers, getTokenTypes, SemanticListener, tokenBuilders } from './semanticTokens';
|
||||
|
||||
type Word = {
|
||||
text: string,
|
||||
|
@ -34,14 +35,16 @@ type Word = {
|
|||
};
|
||||
|
||||
const entries: Entries = getBuiltinEntries();
|
||||
const modules = entries[0].split('\n');
|
||||
const policies = entries[1].split('\n');
|
||||
const variables = entries[2].split('\n');
|
||||
const properties = entries[3].split('\n');
|
||||
export const modules = entries[0].split('\n');
|
||||
export const policies = entries[1].split('\n');
|
||||
export const variables = entries[2].split('\n');
|
||||
export const properties = entries[3].split('\n');
|
||||
|
||||
|
||||
let workspaceFolders: WorkspaceFolder[] | null;
|
||||
let contentChanged = true;
|
||||
|
||||
export let initParams: InitializeParams;
|
||||
|
||||
// Craete a connection for the server, using Node's IPC as a transport.
|
||||
// Also include all preview / proposed LSP features.
|
||||
const connection = createConnection(ProposedFeatures.all);
|
||||
|
@ -50,7 +53,7 @@ const connection = createConnection(ProposedFeatures.all);
|
|||
export const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
|
||||
|
||||
connection.onInitialize((params: InitializeParams) => {
|
||||
workspaceFolders = params.workspaceFolders;
|
||||
initParams = params;
|
||||
const result: InitializeResult = {
|
||||
capabilities: {
|
||||
textDocumentSync: TextDocumentSyncKind.Incremental,
|
||||
|
@ -61,7 +64,15 @@ connection.onInitialize((params: InitializeParams) => {
|
|||
completionProvider: {},
|
||||
documentFormattingProvider: true,
|
||||
documentSymbolProvider: true,
|
||||
definitionProvider: true
|
||||
definitionProvider: true,
|
||||
semanticTokensProvider: {
|
||||
legend: {
|
||||
tokenTypes: getTokenTypes(),
|
||||
tokenModifiers: getTokenModifiers()
|
||||
},
|
||||
range: false,
|
||||
full: true
|
||||
}
|
||||
},
|
||||
serverInfo: {
|
||||
name: 'cmakels',
|
||||
|
@ -256,6 +267,7 @@ connection.onDocumentSymbol((params: DocumentSymbolParams) => {
|
|||
});
|
||||
|
||||
connection.onDefinition((params: DefinitionParams) => {
|
||||
const workspaceFolders = initParams.workspaceFolders;
|
||||
if (workspaceFolders === null || workspaceFolders.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -269,7 +281,7 @@ connection.onDefinition((params: DefinitionParams) => {
|
|||
const word: Word = getWordAtPosition(document, params.position);
|
||||
const wordPos: string = params.textDocument.uri + '_' + params.position.line + '_' +
|
||||
word.col + '_' + word.text;
|
||||
|
||||
|
||||
if (contentChanged) {
|
||||
// clear refToDef and topScope first
|
||||
refToDef.clear();
|
||||
|
@ -299,12 +311,32 @@ connection.onDefinition((params: DefinitionParams) => {
|
|||
});
|
||||
});
|
||||
|
||||
connection.languages.semanticTokens.on(async (params: SemanticTokensParams) => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document === undefined) {
|
||||
return { data: [] };
|
||||
}
|
||||
|
||||
// const builder = getTokenBuilder(document);
|
||||
const docUri: URI = URI.parse(params.textDocument.uri);
|
||||
const tree = getFileContext(docUri);
|
||||
const semanticListener = new SemanticListener(docUri);
|
||||
antlr4.tree.ParseTreeWalker.DEFAULT.walk(semanticListener, tree);
|
||||
|
||||
// return builder.build();
|
||||
return semanticListener.getSemanticTokens();
|
||||
});
|
||||
|
||||
// The content of a text document has changed. This event is emitted
|
||||
// when the text document first opened or when its content has changed.
|
||||
documents.onDidChangeContent(change => {
|
||||
contentChanged = true;
|
||||
});
|
||||
|
||||
documents.onDidClose(event => {
|
||||
tokenBuilders.delete(event.document.uri);
|
||||
});
|
||||
|
||||
function getWordAtPosition(textDocument: TextDocument, position: Position): Word {
|
||||
const lineRange: Range = {
|
||||
start: { line: position.line, character: 0 },
|
||||
|
|
Loading…
Reference in New Issue