Skip to content

Commit

Permalink
Chaning declarations to DocumentSymbol (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
AnHeuermann authored Apr 18, 2024
1 parent ffd4734 commit b9e8a81
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 101 deletions.
53 changes: 47 additions & 6 deletions server/src/analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
import * as LSP from 'vscode-languageserver/node';
import { TextDocument } from 'vscode-languageserver-textdocument';

import Parser = require('web-tree-sitter');
import Parser from 'web-tree-sitter';

import {
getAllDeclarationsInTree
Expand All @@ -51,16 +51,58 @@ import { logger } from './util/logger';

type AnalyzedDocument = {
document: TextDocument,
declarations: LSP.SymbolInformation[],
declarations: LSP.DocumentSymbol[],
tree: Parser.Tree
}

export class MetaModelicaQueries {
public identifierQuery: Parser.Query;
public classTypeQuery: Parser.Query;

constructor(language: Parser.Language) {
this.identifierQuery = language.query('(IDENT) @identifier');
this.classTypeQuery = language.query('(class_type) @type');
}

/**
* Get identifier from node.
*
* @param node Node.
* @returns Identifier
*/
public getIdentifier(node: Parser.SyntaxNode): string | undefined {
const captures = this.identifierQuery.captures(node);
if (captures.length > 0) {
return captures[0].node.text;
} else {
return undefined;
}
}

/**
* Get class type from class_definition node.
*
* @param node Node.
* @returns Class type
*/
public getClassType(node: Parser.SyntaxNode): string | undefined {
const captures = this.classTypeQuery.captures(node);
if (captures.length > 0) {
return captures[0].node.text;
} else {
return undefined;
}
}
}

export default class Analyzer {
private parser: Parser;
private uriToAnalyzedDocument: Record<string, AnalyzedDocument | undefined> = {};
private queries: MetaModelicaQueries;

constructor (parser: Parser) {
this.parser = parser;
this.queries = new MetaModelicaQueries(parser.getLanguage());
}

public analyze(document: TextDocument): LSP.Diagnostic[] {
Expand All @@ -74,7 +116,7 @@ export default class Analyzer {
logger.debug(tree.rootNode.toString());

// Get declarations
const declarations = getAllDeclarationsInTree(tree, uri);
const declarations = getAllDeclarationsInTree(tree, this.queries);

// Update saved analysis for document uri
this.uriToAnalyzedDocument[uri] = {
Expand All @@ -89,15 +131,14 @@ export default class Analyzer {
/**
* Get all symbol declarations in the given file. This is used for generating an outline.
*
* TODO: convert to DocumentSymbol[] which is a hierarchy of symbols found in a given text document.
*/
public getDeclarationsForUri(uri: string): LSP.SymbolInformation[] {
public getDeclarationsForUri(uri: string): LSP.DocumentSymbol[] {
const tree = this.uriToAnalyzedDocument[uri]?.tree;

if (!tree?.rootNode) {
return [];
}

return getAllDeclarationsInTree(tree, uri);
return getAllDeclarationsInTree(tree, this.queries,);
}
}
5 changes: 1 addition & 4 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,7 @@ export class MetaModelicaServer {
* @param params Unused.
* @returns Symbol information.
*/
private onDocumentSymbol(params: LSP.DocumentSymbolParams): LSP.SymbolInformation[] {
// TODO: ideally this should return LSP.DocumentSymbol[] instead of LSP.SymbolInformation[]
// which is a hierarchy of symbols.
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentSymbol
private onDocumentSymbol(params: LSP.DocumentSymbolParams): LSP.DocumentSymbol[] {
logger.debug(`onDocumentSymbol`);
return this.analyzer.getDeclarationsForUri(params.textDocument.uri);
}
Expand Down
143 changes: 85 additions & 58 deletions server/src/util/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,100 +43,126 @@ import * as Parser from 'web-tree-sitter';

import * as TreeSitterUtil from './tree-sitter';
import { logger } from './logger';
import { MetaModelicaQueries } from './../analyzer';

const isEmpty = (data: string): boolean => typeof data === "string" && data.trim().length == 0;

export type GlobalDeclarations = { [word: string]: LSP.SymbolInformation }
export type Declarations = { [word: string]: LSP.SymbolInformation[] }

const GLOBAL_DECLARATION_LEAF_NODE_TYPES = new Set([
'if_statement',
'function_definition',
]);

/**
* Returns all declarations (functions or variables) from a given tree.
* Returns all class declarations from a given tree.
*
* @param tree Tree-sitter tree.
* @param uri The document's uri.
* @returns Symbol information for all declarations.
* @param tree Tree-sitter tree.
* @param queries MetaModelica language queries.
* @returns Symbol information for all declarations.
*/
export function getAllDeclarationsInTree(tree: Parser.Tree, uri: string): LSP.SymbolInformation[] {
const symbols: LSP.SymbolInformation[] = [];
export function getAllDeclarationsInTree(tree: Parser.Tree, queries: MetaModelicaQueries): LSP.DocumentSymbol[] {
const documentSymbols: LSP.DocumentSymbol[] = [];
const cursor = tree.walk();
let reachedRoot = false;

// Walk depth-first to each class_definition node.
// Update DocumentSymbol children when going back up.
while(!reachedRoot) {
const currentNode = cursor.currentNode();

if (cursor.nodeType === "class_definition") {
const symbol = nodeToDocumentSymbol(currentNode, queries, []);
if (symbol) {
documentSymbols.push(symbol);
}
}

TreeSitterUtil.forEach(tree.rootNode, (node) => {
const symbol = getDeclarationSymbolFromNode(node, uri);
if (symbol) {
symbols.push(symbol);
if (cursor.gotoFirstChild()) {
continue;
}
});

return symbols;
if (cursor.gotoNextSibling()) {
continue;
}

let retracing = true;
while (retracing) {
// Try to go to parent
if (cursor.gotoParent()) {
if (cursor.nodeType === "class_definition") {
let tmp = undefined;
if (documentSymbols.length > 1) {
tmp = documentSymbols.pop();
}
if (tmp) {
if (documentSymbols.length > 0) {
documentSymbols[documentSymbols.length - 1].children?.push(tmp);
}
}
}
} else {
retracing = false;
reachedRoot = true;
}

if (cursor.gotoNextSibling()) {
retracing = false;
}
}
}

return documentSymbols;
}

/**
* Converts node to symbol information.
*
* @param tree Tree-sitter tree.
* @param uri The document's uri.
* @returns Symbol information from node.
* @param tree Tree-sitter tree.
* @param queries MetaModelica language queries.
* @param children DocumentSymbol children.
* @returns Symbol information from node.
*/
export function nodeToSymbolInformation(node: Parser.SyntaxNode, uri: string): LSP.SymbolInformation | null {
const named = node.firstNamedChild;

if (named === null) {
export function nodeToDocumentSymbol(node: Parser.SyntaxNode, queries: MetaModelicaQueries, children: LSP.DocumentSymbol[] ): LSP.DocumentSymbol | null {
const name = queries.getIdentifier(node);
if (name === undefined || isEmpty(name)) {
return null;
}

const name = TreeSitterUtil.getIdentifier(node);
if (name === undefined || isEmpty(name)) {
return null;
const detail = [];
if ( node.childForFieldName("encapsulated") ) {
detail.push("encapsulated");
}
if ( node.childForFieldName("partial") ) {
detail.push("partial");
}
detail.push(queries.getClassType(node));

const kind = getKind(node, queries) || LSP.SymbolKind.Variable;

const kind = getKind(node);
const range = TreeSitterUtil.range(node);
const selectionRange = TreeSitterUtil.range(queries.identifierQuery.captures(node)[0].node) || range;

const containerName =
TreeSitterUtil.findParent(node, (p) => p.type === 'function_definition')
?.firstNamedChild?.text || '';
// Walk tree to find next class_definition
const cursor = node.walk();

return LSP.SymbolInformation.create(
return LSP.DocumentSymbol.create(
name,
kind || LSP.SymbolKind.Variable,
TreeSitterUtil.range(node),
uri,
containerName,
detail.join(" "),
kind,
range,
selectionRange,
children
);
}

/**
* Get declaration from node and convert to symbol information.
*
* @param node Root node of tree.
* @param uri The associated URI for this document.
* @returns LSP symbol information for definition.
*/
function getDeclarationSymbolFromNode(node: Parser.SyntaxNode, uri: string): LSP.SymbolInformation | null {
if (TreeSitterUtil.isDefinition(node)) {
return nodeToSymbolInformation(node, uri);
}

return null;
}

/**
* Returns symbol kind from class definition node.
*
* @param node Node containing class_definition
* @returns Symbol kind or `undefined`.
*/
function getKind(node: Parser.SyntaxNode): LSP.SymbolKind | undefined {
function getKind(node: Parser.SyntaxNode, queries: MetaModelicaQueries): LSP.SymbolKind | undefined {

const classTypes = TreeSitterUtil.getClassType(node)?.split(/\s+/);
if (classTypes === undefined) {
const classType = queries.getClassType(node);
if (classType === undefined) {
return undefined;
}

switch (classTypes[classTypes.length - 1]) {
switch (classType) {
case 'class':
case 'optimization':
case 'model':
Expand All @@ -150,6 +176,7 @@ function getKind(node: Parser.SyntaxNode): LSP.SymbolKind | undefined {
case 'uniontype':
return LSP.SymbolKind.Package;
case 'record':
return LSP.SymbolKind.Struct;
case 'type':
return LSP.SymbolKind.TypeParameter;
default:
Expand Down
Loading

0 comments on commit b9e8a81

Please sign in to comment.