refactor: Goto defination

This commit is contained in:
全卓 2022-11-15 18:53:49 +08:00
parent 56db02cc7d
commit b549508f3a
7 changed files with 99 additions and 70 deletions

View File

@ -10,7 +10,8 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"vscode-languageserver": "^8.0.2", "vscode-languageserver": "^8.0.2",
"vscode-languageserver-textdocument": "^1.0.7" "vscode-languageserver-textdocument": "^1.0.7",
"vscode-uri": "^3.0.6"
}, },
"engines": { "engines": {
"node": "*" "node": "*"
@ -53,6 +54,11 @@
"version": "3.17.2", "version": "3.17.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz", "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz",
"integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==" "integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA=="
},
"node_modules/vscode-uri": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.6.tgz",
"integrity": "sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ=="
} }
}, },
"dependencies": { "dependencies": {
@ -87,6 +93,11 @@
"version": "3.17.2", "version": "3.17.2",
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz", "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz",
"integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==" "integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA=="
},
"vscode-uri": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.6.tgz",
"integrity": "sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ=="
} }
} }
} }

View File

@ -13,6 +13,7 @@
}, },
"dependencies": { "dependencies": {
"vscode-languageserver": "^8.0.2", "vscode-languageserver": "^8.0.2",
"vscode-languageserver-textdocument": "^1.0.7" "vscode-languageserver-textdocument": "^1.0.7",
"vscode-uri": "^3.0.6"
} }
} }

View File

@ -22,7 +22,10 @@ import CMakeLexer from './parser/CMakeLexer.js';
import CMakeParser from './parser/CMakeParser.js'; import CMakeParser from './parser/CMakeParser.js';
import { SymbolListener } from './docSymbols'; import { SymbolListener } from './docSymbols';
import { Entries, getBuiltinEntries, getCMakeVersion, getFileContext } from './utils'; import { Entries, getBuiltinEntries, getCMakeVersion, getFileContext } from './utils';
import { DefinationListener, refToDef, topScope } from './symbolTable/goToDefination'; import { DefinationListener, incToBaseDir, refToDef, topScope } from './symbolTable/goToDefination';
import { existsSync } from 'fs';
import { URI, Utils } from 'vscode-uri';
import path = require('path');
type Word = { type Word = {
text: string, text: string,
@ -261,18 +264,26 @@ connection.onDefinition((params: DefinitionParams) => {
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const uri: string = params.textDocument.uri; let rootFile = workspaceFolders[0].uri + '/CMakeLists.txt';
const tree = getFileContext(uri); let rootFileURI: URI = URI.parse(rootFile);
const definationListener = new DefinationListener(uri, topScope); if (!existsSync(rootFileURI.fsPath)) {
rootFile = params.textDocument.uri;
rootFileURI = URI.parse(rootFile);
}
const baseDir: URI = Utils.dirname(rootFileURI);
const tree = getFileContext(rootFileURI);
const definationListener = new DefinationListener(baseDir, rootFileURI, topScope);
// clear refToDef and topScope first // clear refToDef and topScope first
refToDef.clear(); refToDef.clear();
topScope.clear(); topScope.clear();
incToBaseDir.clear();
antlr4.tree.ParseTreeWalker.DEFAULT.walk(definationListener, tree); antlr4.tree.ParseTreeWalker.DEFAULT.walk(definationListener, tree);
const document = documents.get(uri); const document = documents.get(params.textDocument.uri);
const word: Word = getWordAtPosition(document, params.position); const word: Word = getWordAtPosition(document, params.position);
const wordPos: string = uri + '_' + params.position.line + '_' + const wordPos: string = params.textDocument.uri + '_' + params.position.line + '_' +
word.col + '_' + word.text; word.col + '_' + word.text;
if (refToDef.has(wordPos)) { if (refToDef.has(wordPos)) {

View File

@ -1,12 +1,13 @@
import Token from "../parser/antlr4/Token"; import Token from "../parser/antlr4/Token";
import CMakeListener from "../parser/CMakeListener"; import CMakeListener from "../parser/CMakeListener";
import CMakeParser from "../parser/CMakeParser"; import CMakeParser from "../parser/CMakeParser";
import { DefinationListener, refToDef } from "./goToDefination"; import { DefinationListener, incToBaseDir, refToDef } from "./goToDefination";
import { FileScope, FunctionScope, MacroScope, Scope } from "./scope"; import { FileScope, FunctionScope, MacroScope, Scope } from "./scope";
import { Sym, Type } from "./symbol"; import { Sym, Type } from "./symbol";
import { getFileContext, getIncludeFileUri, getSubCMakeListsUri } from "../utils"; import { getFileContext, getIncludeFileUri, getSubCMakeListsUri } from "../utils";
import antlr4 from "../parser/antlr4"; import antlr4 from "../parser/antlr4";
import ParseTree from "../parser/antlr4/tree/ParseTree"; import ParseTree from "../parser/antlr4/tree/ParseTree";
import { URI } from "vscode-uri";
export class FuncMacroListener extends CMakeListener { export class FuncMacroListener extends CMakeListener {
private currentScope: Scope; private currentScope: Scope;
@ -78,8 +79,11 @@ export class FuncMacroListener extends CMakeListener {
} }
const nameToken = ctx.argument(0).start; const nameToken = ctx.argument(0).start;
const fileUri: string = getIncludeFileUri(this.funcMacroSym.getUri(), nameToken.text);
if (!fileUri) { // 获取包含该函数定义的文件的基路径
const baseDir: URI = incToBaseDir.get(this.funcMacroSym.getUri().fsPath);
const incUri: URI = getIncludeFileUri(baseDir, nameToken.text);
if (!incUri) {
return; return;
} }
@ -87,7 +91,7 @@ export class FuncMacroListener extends CMakeListener {
const refPos: string = this.funcMacroSym.getUri() + '_' + (nameToken.line - 1) + '_' + const refPos: string = this.funcMacroSym.getUri() + '_' + (nameToken.line - 1) + '_' +
nameToken.column + '_' + nameToken.text; nameToken.column + '_' + nameToken.text;
refToDef.set(refPos, { refToDef.set(refPos, {
uri: fileUri, uri: incUri.toString(),
range: { range: {
start: { start: {
line: 0, line: 0,
@ -100,8 +104,8 @@ export class FuncMacroListener extends CMakeListener {
} }
}); });
const tree = getFileContext(fileUri); const tree = getFileContext(incUri);
const definationListener = new DefinationListener(fileUri, this.currentScope); const definationListener = new DefinationListener(baseDir, incUri, this.currentScope);
antlr4.tree.ParseTreeWalker.DEFAULT.walk(definationListener, tree); antlr4.tree.ParseTreeWalker.DEFAULT.walk(definationListener, tree);
} }
@ -112,8 +116,9 @@ export class FuncMacroListener extends CMakeListener {
} }
const dirToken: Token = ctx.argument(0).start; const dirToken: Token = ctx.argument(0).start;
const fileUri: string = getSubCMakeListsUri(this.funcMacroSym.getUri(), dirToken.text); const baseDir: URI = incToBaseDir.get(this.funcMacroSym.getUri().fsPath);
if (!fileUri) { const subCMakeListsUri: URI = getSubCMakeListsUri(baseDir, dirToken.text);
if (!subCMakeListsUri) {
return; return;
} }
@ -123,7 +128,7 @@ export class FuncMacroListener extends CMakeListener {
const refPos: string = this.funcMacroSym.getUri() + '_' + (dirToken.line - 1) + '_' + const refPos: string = this.funcMacroSym.getUri() + '_' + (dirToken.line - 1) + '_' +
dirToken.column + '_' + dirToken.text; dirToken.column + '_' + dirToken.text;
refToDef.set(refPos, { refToDef.set(refPos, {
uri: fileUri, uri: subCMakeListsUri.toString(),
range: { range: {
start: { start: {
line: 0, line: 0,
@ -136,10 +141,10 @@ export class FuncMacroListener extends CMakeListener {
} }
}); });
const tree = getFileContext(fileUri); const tree = getFileContext(subCMakeListsUri);
const subDirScope: Scope = new FileScope(this.currentScope); const subDirScope: Scope = new FileScope(this.currentScope);
this.currentScope = subDirScope; this.currentScope = subDirScope;
const definationListener = new DefinationListener(fileUri, subDirScope); const definationListener = new DefinationListener(baseDir, subCMakeListsUri, subDirScope);
antlr4.tree.ParseTreeWalker.DEFAULT.walk(definationListener, tree); antlr4.tree.ParseTreeWalker.DEFAULT.walk(definationListener, tree);
} }

View File

@ -1,14 +1,16 @@
import { Location } from "vscode-languageserver-types"; import { Location } from "vscode-languageserver-types";
import { URI, Utils } from "vscode-uri";
import antlr4 from '../parser/antlr4/index.js'; import antlr4 from '../parser/antlr4/index.js';
import Token from "../parser/antlr4/Token"; import Token from "../parser/antlr4/Token";
import ParseTree from "../parser/antlr4/tree/ParseTree.js"; import ParseTree from "../parser/antlr4/tree/ParseTree.js";
import CMakeListener from "../parser/CMakeListener"; import CMakeListener from "../parser/CMakeListener";
import { getFileContext, getIncludeFileUri, getSubCMakeListsUri } from "../utils"; import { getFileContext, getIncludeFileUri, getSubCMakeListsUri } from "../utils";
import { FuncMacroListener } from "./function"; import { FuncMacroListener } from "./function";
import { FileScope, FunctionScope, Scope } from "./scope"; import { FileScope, Scope } from "./scope";
import { Sym, Type } from "./symbol"; import { Sym, Type } from "./symbol";
export const topScope: FileScope = new FileScope(null); export const topScope: FileScope = new FileScope(null);
export const incToBaseDir: Map<string, URI> = new Map<string, URI>();
/** /**
* key: <uri>_<line>_<column>_<word> * key: <uri>_<line>_<column>_<word>
@ -21,14 +23,17 @@ export const refToDef: Map<string, Location> = new Map();
export class DefinationListener extends CMakeListener { export class DefinationListener extends CMakeListener {
private currentScope: Scope; private currentScope: Scope;
private inBody = false; private inBody = false;
private uri: string; private curFile: URI; // current file uri
private baseDir: URI; // directory used by include/add_subdirectory commands
private parseTreeProperty = new Map<ParseTree, boolean>(); private parseTreeProperty = new Map<ParseTree, boolean>();
constructor(uri: string, scope: Scope) { constructor(baseDir: URI, curFile: URI, scope: Scope) {
super(); super();
this.uri = uri; this.curFile = curFile;
this.currentScope = scope; this.currentScope = scope;
this.baseDir = baseDir;
// Utils.dirname(URI.parse(uri)).fsPath
} }
enterFunctionCmd(ctx: any): void { enterFunctionCmd(ctx: any): void {
@ -37,7 +42,7 @@ export class DefinationListener extends CMakeListener {
// create a function symbol // create a function symbol
const funcToken: Token = ctx.argument(0).start; const funcToken: Token = ctx.argument(0).start;
const funcSymbol: Sym = new Sym(funcToken.text, Type.Function, const funcSymbol: Sym = new Sym(funcToken.text, Type.Function,
this.uri, funcToken.line - 1, funcToken.column); this.curFile, funcToken.line - 1, funcToken.column);
// add to current scope // add to current scope
this.currentScope.define(funcSymbol); this.currentScope.define(funcSymbol);
@ -53,7 +58,7 @@ export class DefinationListener extends CMakeListener {
// create a macro symbol // create a macro symbol
const macroToken: Token = ctx.argument(0).start; const macroToken: Token = ctx.argument(0).start;
const macroSymbol: Sym = new Sym(macroToken.text, Type.Macro, const macroSymbol: Sym = new Sym(macroToken.text, Type.Macro,
this.uri, macroToken.line - 1, macroToken.column); this.curFile, macroToken.line - 1, macroToken.column);
// add macro to this scope // add macro to this scope
this.currentScope.define(macroSymbol); this.currentScope.define(macroSymbol);
@ -71,7 +76,7 @@ export class DefinationListener extends CMakeListener {
// create a variable symbol // create a variable symbol
const varToken: Token = ctx.argument(0).start; const varToken: Token = ctx.argument(0).start;
const varSymbol: Sym = new Sym(varToken.text, Type.Variable, const varSymbol: Sym = new Sym(varToken.text, Type.Variable,
this.uri, varToken.line - 1, varToken.column); this.curFile, varToken.line - 1, varToken.column);
// add variable to current scope // add variable to current scope
this.currentScope.define(varSymbol); this.currentScope.define(varSymbol);
@ -87,16 +92,18 @@ export class DefinationListener extends CMakeListener {
} }
const nameToken = ctx.argument(0).start; const nameToken = ctx.argument(0).start;
const fileUri: string = getIncludeFileUri(this.uri, nameToken.text); const incUri: URI = getIncludeFileUri(this.baseDir, nameToken.text);
if (!fileUri) { if (!incUri) {
return; return;
} }
incToBaseDir.set(incUri.toString(), this.baseDir);
// add included module to refDef // add included module to refDef
const refPos: string = this.uri + '_' + (nameToken.line - 1) + '_' + const refPos: string = this.curFile + '_' + (nameToken.line - 1) + '_' +
nameToken.column + '_' + nameToken.text; nameToken.column + '_' + nameToken.text;
refToDef.set(refPos, { refToDef.set(refPos, {
uri: fileUri, uri: incUri.toString(),
range: { range: {
start: { start: {
line: 0, line: 0,
@ -109,8 +116,8 @@ export class DefinationListener extends CMakeListener {
} }
}); });
const tree = getFileContext(fileUri); const tree = getFileContext(incUri);
const definationListener = new DefinationListener(fileUri, this.currentScope); const definationListener = new DefinationListener(this.baseDir, incUri, this.currentScope);
antlr4.tree.ParseTreeWalker.DEFAULT.walk(definationListener, tree); antlr4.tree.ParseTreeWalker.DEFAULT.walk(definationListener, tree);
} }
@ -122,18 +129,18 @@ export class DefinationListener extends CMakeListener {
} }
const dirToken: Token = ctx.argument(0).start; const dirToken: Token = ctx.argument(0).start;
const fileUri: string = getSubCMakeListsUri(this.uri, dirToken.text); const subCMakeListsUri: URI = getSubCMakeListsUri(this.baseDir, dirToken.text);
if (!fileUri) { if (!subCMakeListsUri) {
return; return;
} }
this.parseTreeProperty.set(ctx, true); this.parseTreeProperty.set(ctx, true);
// add subdir CMakeLists.txt to refDef // add subdir CMakeLists.txt to refDef
const refPos: string = this.uri + '_' + (dirToken.line - 1) + '_' + const refPos: string = this.curFile + '_' + (dirToken.line - 1) + '_' +
dirToken.column + '_' + dirToken.text; dirToken.column + '_' + dirToken.text;
refToDef.set(refPos, { refToDef.set(refPos, {
uri: fileUri, uri: subCMakeListsUri.toString(),
range: { range: {
start: { start: {
line: 0, line: 0,
@ -146,10 +153,12 @@ export class DefinationListener extends CMakeListener {
} }
}); });
const tree = getFileContext(fileUri); const tree = getFileContext(subCMakeListsUri);
const subDirScope: Scope = new FileScope(this.currentScope); const subDirScope: Scope = new FileScope(this.currentScope);
// FIXME: 此处是否应该切换作用域?
this.currentScope = subDirScope; this.currentScope = subDirScope;
const definationListener = new DefinationListener(fileUri, subDirScope); const subBaseDir: URI = Utils.joinPath(this.baseDir, dirToken.text);
const definationListener = new DefinationListener(subBaseDir, subCMakeListsUri, subDirScope);
antlr4.tree.ParseTreeWalker.DEFAULT.walk(definationListener, tree); antlr4.tree.ParseTreeWalker.DEFAULT.walk(definationListener, tree);
} }
@ -176,7 +185,7 @@ export class DefinationListener extends CMakeListener {
} }
// token.line start from 1, so - 1 first // token.line start from 1, so - 1 first
const refPos: string = this.uri + '_' + (cmdToken.line - 1) + '_' + const refPos: string = this.curFile + '_' + (cmdToken.line - 1) + '_' +
cmdToken.column + '_' + cmdToken.text; cmdToken.column + '_' + cmdToken.text;
// add to refToDef // add to refToDef
@ -216,7 +225,7 @@ export class DefinationListener extends CMakeListener {
} }
// token.line start from 1, so - 1 first // token.line start from 1, so - 1 first
const refPos: string = this.uri + '_' + (argToken.line - 1) + '_' + const refPos: string = this.curFile + '_' + (argToken.line - 1) + '_' +
(argToken.column + match.index + 2) + '_' + varRef; (argToken.column + match.index + 2) + '_' + varRef;
refToDef.set(refPos, symbol.getLocation()); refToDef.set(refPos, symbol.getLocation());
} }

View File

@ -1,4 +1,5 @@
import { Location } from 'vscode-languageserver-types'; import { Location } from 'vscode-languageserver-types';
import { URI } from 'vscode-uri';
import { Scope } from './scope'; import { Scope } from './scope';
export enum Type { export enum Type {
@ -14,11 +15,11 @@ export class Sym {
private type: Type; private type: Type;
private scope: Scope; // all symbols know what scope contains them private scope: Scope; // all symbols know what scope contains them
private name: string; private name: string;
private uri: string; private uri: URI;
private line: number; private line: number;
private column: number; private column: number;
constructor(name: string, type: Type, uri: string, line: number, column: number) { constructor(name: string, type: Type, uri: URI, line: number, column: number) {
this.name = name; this.name = name;
this.type = type; this.type = type;
this.uri = uri; this.uri = uri;
@ -40,7 +41,7 @@ export class Sym {
getLocation(): Location { getLocation(): Location {
return { return {
uri: this.uri, uri: this.uri.toString(),
range: { range: {
start: { start: {
line: this.line, line: this.line,
@ -54,7 +55,7 @@ export class Sym {
}; };
} }
getUri(): string { getUri(): URI {
return this.uri; return this.uri;
} }

View File

@ -1,15 +1,15 @@
import * as cp from 'child_process'; import * as cp from 'child_process';
import { documents } from './server'; import { documents } from './server';
import { existsSync, fstat } from 'fs'; import { existsSync } from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import { fileURLToPath, pathToFileURL } from 'url';
import antlr4 from './parser/antlr4/index.js'; import antlr4 from './parser/antlr4/index.js';
import CMakeLexer from "./parser/CMakeLexer"; import CMakeLexer from "./parser/CMakeLexer";
import CMakeParser from "./parser/CMakeParser"; import CMakeParser from "./parser/CMakeParser";
import InputStream from './parser/antlr4/InputStream'; import InputStream from './parser/antlr4/InputStream';
import { URI, Utils } from 'vscode-uri';
export type Entries = [string, string, string, string]; export type Entries = [string, string, string, string];
@ -44,13 +44,13 @@ export function getCMakeVersion(): CMakeVersion {
}; };
} }
export function getFileContext(uri: string) { export function getFileContext(uri: URI) {
const document = documents.get(uri); const document = documents.get(uri.toString());
let text: string; let text: string;
if (document) { if (document) {
text = document.getText(); text = document.getText();
} else { } else {
text = fs.readFileSync(fileURLToPath(uri), { encoding: 'utf-8' }); text = fs.readFileSync(uri.fsPath, { encoding: 'utf-8' });
} }
const input: InputStream = antlr4.CharStreams.fromString(text); const input: InputStream = antlr4.CharStreams.fromString(text);
const lexer = new CMakeLexer(input); const lexer = new CMakeLexer(input);
@ -59,31 +59,21 @@ export function getFileContext(uri: string) {
return parser.file(); return parser.file();
} }
export function getSubCMakeListsUri(currentFileUri: string, subDir: string): string { export function getSubCMakeListsUri(baseDir: URI, subDir: string): URI {
const currentFilePath: string = fileURLToPath(currentFileUri); const subCMakeListsUri: URI = Utils.joinPath(baseDir, subDir, 'CMakeLists.txt');
const subCMakeListsPath = path.join(currentFilePath, '..', subDir, 'CMakeLists.txt'); if (existsSync(subCMakeListsUri.fsPath)) {
if (existsSync(subCMakeListsPath)) { return subCMakeListsUri;
return pathToFileURL(subCMakeListsPath).toString();
} }
return null; return null;
} }
export function getIncludeFileUri(currentFileUri: string, includeFileName: string): string { export function getIncludeFileUri(baseDir: URI, includeFileName: string): URI {
const currentFilePath: string = fileURLToPath(currentFileUri); const incFileUri: URI = Utils.joinPath(baseDir, includeFileName);
const includeFilePath: string = path.dirname(currentFilePath) + path.sep + includeFileName; if (existsSync(incFileUri.fsPath)) {
// const includeFileUri: string = filePathToURL; return incFileUri;
if (existsSync(includeFilePath)) {
const index = currentFileUri.lastIndexOf('/');
const includeFileUri = currentFileUri.slice(0, index) + path.sep + includeFileName;
return includeFileUri;
} }
// TODO: if includeFileName contains variable reference
// ex: include(${CMAKE_CURRENT_LIST_DIR}/xxx.cmake)
// Module: AndroidTestUtilities
// name is a cmake module
const cmakePath: string = which('cmake'); const cmakePath: string = which('cmake');
if (cmakePath === null) { if (cmakePath === null) {
return null; return null;
@ -93,7 +83,8 @@ export function getIncludeFileUri(currentFileUri: string, includeFileName: strin
const resPath = path.join(cmakePath, '../..', 'share', moduleDir, 'Modules', includeFileName) + '.cmake'; const resPath = path.join(cmakePath, '../..', 'share', moduleDir, 'Modules', includeFileName) + '.cmake';
if (existsSync(resPath)) { if (existsSync(resPath)) {
return pathToFileURL(resPath).toString(); // return pathToFileURL(resPath).toString();
return URI.file(resPath);
} }
return null; return null;