Skip to content

Commit

Permalink
Add incremental parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
kerristrasz committed May 22, 2024
1 parent 860ea6c commit 94378f6
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 35 deletions.
6 changes: 2 additions & 4 deletions server/src/analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,13 @@
*/

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 * as path from 'node:path';
import * as fs from 'node:fs/promises';
import * as fsSync from 'node:fs';
import * as url from 'node:url';

import { ModelicaDocument, ModelicaLibrary, ModelicaProject } from './project';

import { getAllDeclarationsInTree } from './util/declarations';
import { logger } from './util/logger';

Expand Down Expand Up @@ -111,7 +109,7 @@ export default class Analyzer {
* @param text the modification
* @param range range to update, or `undefined` to replace the whole file
*/
public updateDocument(uri: LSP.DocumentUri, text: string): void {
public updateDocument(uri: LSP.DocumentUri, text: string, range?: LSP.Range): void {
this.#project.updateDocument(url.fileURLToPath(uri), text);
}

Expand Down
81 changes: 62 additions & 19 deletions server/src/project/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,17 @@
*
*/

import { TextDocument } from "vscode-languageserver-textdocument";
import * as LSP from "vscode-languageserver/node";
import Parser from "web-tree-sitter";
import * as fs from "node:fs/promises";
import * as url from "node:url";
import * as path from "node:path";

import { logger } from "../util/logger";
import { ModelicaLibrary } from "./library";
import { ModelicaProject } from "./project";
import { TextDocument } from 'vscode-languageserver-textdocument';
import * as LSP from 'vscode-languageserver/node';
import Parser from 'web-tree-sitter';
import * as fs from 'node:fs/promises';
import * as url from 'node:url';
import * as path from 'node:path';

import { logger } from '../util/logger';
import { positionToPoint } from '../util/tree-sitter';
import { ModelicaLibrary } from './library';
import { ModelicaProject } from './project';

export class ModelicaDocument implements TextDocument {
readonly #library: ModelicaLibrary;
Expand All @@ -68,26 +69,68 @@ export class ModelicaDocument implements TextDocument {
): Promise<ModelicaDocument> {
logger.debug(`Loading document at '${documentPath}'...`);

const content = await fs.readFile(documentPath, "utf-8");
const content = await fs.readFile(documentPath, 'utf-8');
// On caching: see issue https://github.com/tree-sitter/tree-sitter/issues/824
// TL;DR: it's faster to re-parse the content than it is to deserialize the cached tree.
const tree = library.project.parser.parse(content);

return new ModelicaDocument(
library,
TextDocument.create(url.fileURLToPath(documentPath), "modelica", 0, content),
tree
TextDocument.create(url.fileURLToPath(documentPath), 'modelica', 0, content),
tree,
);
}

/**
* Updates a document.
*
* @param text the modification
* @param range the range to update, or `undefined` to replace the whole file
*/
public async update(text: string): Promise<void> {
TextDocument.update(this.#document, [{ text }], this.version + 1);
this.#tree = this.project.parser.parse(text);
return;
public async update(text: string, range?: LSP.Range): Promise<void> {
if (range === undefined) {
TextDocument.update(this.#document, [{ text }], this.version + 1);
this.#tree = this.project.parser.parse(text);
return;
}

const startIndex = this.offsetAt(range.start);
const startPosition = positionToPoint(range.start);
const oldEndIndex = this.offsetAt(range.end);
const oldEndPosition = positionToPoint(range.end);
const newEndIndex = startIndex + text.length;

TextDocument.update(this.#document, [{ text, range }], this.version + 1);
const newEndPosition = positionToPoint(this.positionAt(newEndIndex));

this.#tree.edit({
startIndex,
startPosition,
oldEndIndex,
oldEndPosition,
newEndIndex,
newEndPosition,
});

this.#tree = this.project.parser.parse((index: number, position?: Parser.Point) => {
if (position) {
return this.getText({
start: {
character: position.column,
line: position.row,
},
end: {
character: position.column + 1,
line: position.row,
},
});
} else {
return this.getText({
start: this.positionAt(index),
end: this.positionAt(index + 1),
});
}
}, this.#tree);
}

public getText(range?: LSP.Range | undefined): string {
Expand Down Expand Up @@ -132,8 +175,8 @@ export class ModelicaDocument implements TextDocument {
const fileName = directories.pop()!;

const packagePath: string[] = [this.#library.name, ...directories];
if (fileName !== "package.mo") {
packagePath.push(fileName.slice(0, fileName.length - ".mo".length));
if (fileName !== 'package.mo') {
packagePath.push(fileName.slice(0, fileName.length - '.mo'.length));
}

return packagePath;
Expand Down
19 changes: 9 additions & 10 deletions server/src/project/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@
*
*/

import Parser from "web-tree-sitter";
import * as LSP from "vscode-languageserver";
import url from "node:url";
import path from "node:path";
import Parser from 'web-tree-sitter';
import * as LSP from 'vscode-languageserver';
import url from 'node:url';
import path from 'node:path';

import { ModelicaLibrary } from "./library";
import { ModelicaLibrary } from './library';
import { ModelicaDocument } from './document';
import { logger } from "../util/logger";
import { logger } from '../util/logger';

export class ModelicaProject {
readonly #parser: Parser;
Expand Down Expand Up @@ -91,7 +91,7 @@ export class ModelicaProject {

for (const library of this.#libraries) {
const relative = path.relative(library.path, documentPath);
const isSubdirectory = relative && !relative.startsWith("..") && !path.isAbsolute(relative);
const isSubdirectory = relative && !relative.startsWith('..') && !path.isAbsolute(relative);

// Assume that files can't be inside multiple libraries at the same time
if (!isSubdirectory) {
Expand All @@ -118,12 +118,12 @@ export class ModelicaProject {
* @param text the modification
* @param range range to update, or undefined to replace the whole file
*/
public updateDocument(documentPath: string, text: string): void {
public updateDocument(documentPath: string, text: string, range?: LSP.Range): void {
logger.debug(`Updating document at '${documentPath}'...`);

const doc = this.getDocument(documentPath);
if (doc) {
doc.update(text);
doc.update(text, range);
logger.debug(`Updated document '${documentPath}'`);
} else {
logger.warn(`Failed to update document '${documentPath}': not loaded`);
Expand All @@ -147,5 +147,4 @@ export class ModelicaProject {
public get parser(): Parser {
return this.#parser;
}

}
5 changes: 3 additions & 2 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export class ModelicaServer {
documentSymbolProvider: true,
colorProvider: false,
semanticTokensProvider: undefined,
textDocumentSync: LSP.TextDocumentSyncKind.Full,
textDocumentSync: LSP.TextDocumentSyncKind.Incremental,
workspace: {
workspaceFolders: {
supported: true,
Expand Down Expand Up @@ -148,7 +148,8 @@ export class ModelicaServer {
private async onDidChangeTextDocument(params: LSP.DidChangeTextDocumentParams): Promise<void> {
logger.debug('onDidChangeTextDocument');
for (const change of params.contentChanges) {
this.#analyzer.updateDocument(params.textDocument.uri, change.text);
const range = 'range' in change ? change.range : undefined;
this.#analyzer.updateDocument(params.textDocument.uri, change.text, range);
}
}

Expand Down
9 changes: 9 additions & 0 deletions server/src/util/tree-sitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
* -----------------------------------------------------------------------------
*/

import Parser from 'web-tree-sitter';
import * as LSP from 'vscode-languageserver/node';
import { SyntaxNode } from 'web-tree-sitter';

Expand Down Expand Up @@ -169,3 +170,11 @@ export function getClassPrefixes(node: SyntaxNode): string | null {

return classPrefixNode.text;
}

export function positionToPoint(position: LSP.Position): Parser.Point {
return { row: position.line, column: position.character };
}

export function pointToPosition(point: Parser.Point): LSP.Position {
return { line: point.row, character: point.column };
}

0 comments on commit 94378f6

Please sign in to comment.