Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chaning declarations to DocumentSymbol #1

Merged
merged 1 commit into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading