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

Add incremental parsing support #25

Merged
merged 3 commits into from
Jun 3, 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
10 changes: 5 additions & 5 deletions server/src/analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ import * as fsSync from 'node:fs';
import * as url from 'node:url';

import { ModelicaDocument, ModelicaLibrary, ModelicaProject } from './project';
import { uriToPath } from "./util";
import { getAllDeclarationsInTree } from './util/declarations';
import { logger } from './util/logger';
import { uriToPath } from './util';

export default class Analyzer {
#project: ModelicaProject;
Expand Down Expand Up @@ -98,8 +98,8 @@ export default class Analyzer {
* @param uri uri to document to add
* @throws if the document does not belong to a library
*/
public addDocument(uri: LSP.DocumentUri): void {
this.#project.addDocument(uriToPath(uri));
public async addDocument(uri: LSP.DocumentUri): Promise<void> {
await this.#project.addDocument(uriToPath(uri));
}

/**
Expand All @@ -110,8 +110,8 @@ export default class Analyzer {
* @param text the modification
* @param range range to update, or `undefined` to replace the whole file
*/
public async updateDocument(uri: LSP.DocumentUri, text: string): Promise<void> {
await this.#project.updateDocument(uriToPath(uri), text);
public async updateDocument(uri: LSP.DocumentUri, text: string, range?: LSP.Range): Promise<void> {
await this.#project.updateDocument(uriToPath(uri), text, range);
}

/**
Expand Down
66 changes: 57 additions & 9 deletions server/src/project/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,24 @@ import Parser from 'web-tree-sitter';
import * as fs from 'node:fs/promises';
import * as TreeSitterUtil from '../util/tree-sitter';

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

export class ModelicaDocument implements TextDocument {
readonly #project: ModelicaProject;
readonly #library: ModelicaLibrary | null;
readonly #document: TextDocument;
#tree: Parser.Tree;

public constructor(project: ModelicaProject, library: ModelicaLibrary | null, document: TextDocument, tree: Parser.Tree) {
public constructor(
project: ModelicaProject,
library: ModelicaLibrary | null,
document: TextDocument,
tree: Parser.Tree,
) {
this.#project = project;
this.#library = library;
this.#document = document;
Expand Down Expand Up @@ -90,12 +96,54 @@ export class ModelicaDocument implements TextDocument {

/**
* 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 @@ -138,19 +186,19 @@ export class ModelicaDocument implements TextDocument {
public get within(): string[] {
const withinClause = this.#tree.rootNode.children
.find((node) => node.type === 'within_clause')
?.childForFieldName("name");
?.childForFieldName('name');
if (!withinClause) {
return [];
}

// TODO: Use a helper function from TreeSitterUtil
const identifiers: string[] = [];
TreeSitterUtil.forEach(withinClause, (node) => {
if (node.type === "name") {
if (node.type === 'name') {
return true;
}

if (node.type === "IDENT") {
if (node.type === 'IDENT') {
identifiers.push(node.text);
}

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';

/** Options for {@link ModelicaProject.getDocument} */
export interface GetDocumentOptions {
Expand Down Expand Up @@ -117,7 +117,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 Down Expand Up @@ -155,12 +155,12 @@ export class ModelicaProject {
* @param text the modification
* @returns if the document was updated
*/
public async updateDocument(documentPath: string, text: string): Promise<boolean> {
public async updateDocument(documentPath: string, text: string, range?: LSP.Range): Promise<boolean> {
logger.debug(`Updating document at '${documentPath}'...`);

const doc = await this.getDocument(documentPath, { load: true });
if (doc) {
doc.update(text);
doc.update(text, range);
logger.debug(`Updated document '${documentPath}'`);
return true;
} else {
Expand Down Expand Up @@ -191,5 +191,4 @@ export class ModelicaProject {
public get parser(): Parser {
return this.#parser;
}

}
40 changes: 40 additions & 0 deletions server/src/project/test/document.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,46 @@ describe('ModelicaDocument', () => {
assert.equal(document.getText().trim(), UPDATED_TEST_PACKAGE_CONTENT.trim());
});

it('can update incrementally', () => {
const textDocument = createTextDocument('.', TEST_PACKAGE_CONTENT);
const tree = project.parser.parse(TEST_PACKAGE_CONTENT);
const document = new ModelicaDocument(project, library, textDocument, tree);
document.update(
'1.0.1',
{
start: {
line: 1,
character: 22,
},
end: {
line: 1,
character: 27,
},
}
);

assert.equal(document.getText().trim(), UPDATED_TEST_PACKAGE_CONTENT.trim());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an additional assert to test that the tree indeed is updated. In this case it's hard to tell, so maybe do an update that does change the tree. Maybe adding another annotation or model or so.

Otherwise we aren't sure that the tree was updated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ups, wrong button. I wanted to request changes on my review^^. Please check if you can update the test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like this?


document.update(
'\n model A\n end A;',
{
start: {
line: 1,
character: 30,
},
end: {
line: 1,
character: 30,
},
}
);

const model = document.tree.rootNode.descendantsOfType("class_definition")[1];
assert.equal(model.type, "class_definition");
assert.equal(model.descendantsOfType("IDENT")[0].text, "A");
assert.equal(document.tree.rootNode.descendantsOfType("annotation_clause").length, 1);
});

it('a file with no `within` clause has the correct package path', () => {
const textDocument = createTextDocument('./package.mo', TEST_PACKAGE_CONTENT);
const tree = project.parser.parse(TEST_PACKAGE_CONTENT);
Expand Down
7 changes: 4 additions & 3 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,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 @@ -150,7 +150,8 @@ export class ModelicaServer {
private async onDidChangeTextDocument(params: LSP.DidChangeTextDocumentParams): Promise<void> {
logger.debug('onDidChangeTextDocument');
for (const change of params.contentChanges) {
await this.#analyzer.updateDocument(params.textDocument.uri, change.text);
const range = 'range' in change ? change.range : undefined;
await this.#analyzer.updateDocument(params.textDocument.uri, change.text, range);
}
}

Expand All @@ -160,7 +161,7 @@ export class ModelicaServer {
for (const change of params.changes) {
switch (change.type) {
case LSP.FileChangeType.Created:
this.#analyzer.addDocument(change.uri);
await this.#analyzer.addDocument(change.uri);
break;
case LSP.FileChangeType.Changed: {
// TODO: incremental?
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 };
}
Loading