Skip to content
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
400 changes: 259 additions & 141 deletions client/src/syntaxes/vba.tmLanguage.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"icon": "images/vba-lsp-icon.png",
"author": "SSlinky",
"license": "MIT",
"version": "1.2.0",
"version": "1.3.0",
"repository": {
"type": "git",
"url": "https://github.com/SSlinky/VBA-LanguageServer"
Expand Down
47 changes: 23 additions & 24 deletions server/src/project/document.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Diagnostic, SemanticTokens, SymbolInformation, SymbolKind } from 'vscode-languageserver';
import { CancellationToken, Diagnostic, LSPErrorCodes, ResponseError, SemanticTokens, SymbolInformation, SymbolKind } from 'vscode-languageserver';
import { Workspace } from './workspace';
import { FoldableElement } from './elements/special';
import { BaseSyntaxElement, HasAttribute, HasSemanticToken, HasSymbolInformation } from './elements/base';
Expand All @@ -9,17 +9,7 @@ import { SemanticTokensManager } from '../capabilities/semanticTokens';
import { sleep } from '../utils/helpers';


export interface ProjectDocument {
name: string;
textDocument: TextDocument;
languageServerSemanticTokens: (range?: Range) => SemanticTokens | null;
languageServerSymbolInformationAsync(): Promise<SymbolInformation[]>;
get foldableElements(): FoldingRange[];
parse(): void;
}


export abstract class BaseProjectDocument implements ProjectDocument {
export abstract class BaseProjectDocument {
readonly workspace: Workspace;
readonly textDocument: TextDocument;
readonly name: string;
Expand All @@ -38,10 +28,6 @@ export abstract class BaseProjectDocument implements ProjectDocument {
isBusy = false;
abstract symbolKind: SymbolKind

get foldableElements() {
return this._foldableElements;
}

get activeAttributeElement() {
return this._attributeElements?.at(-1);
}
Expand All @@ -52,7 +38,7 @@ export abstract class BaseProjectDocument implements ProjectDocument {
this.name = name;
}

static create(workspace: Workspace, document: TextDocument): VbaClassDocument | VbaModuleDocument {
static create(workspace: Workspace, document: TextDocument): BaseProjectDocument {
const slashParts = document.uri.split('/').at(-1);
const dotParts = slashParts?.split('.');
const extension = dotParts?.at(-1);
Expand All @@ -76,19 +62,21 @@ export abstract class BaseProjectDocument implements ProjectDocument {
return this._semanticTokens.getSemanticTokens(range);
};

async languageServerSymbolInformationAsync() {
async languageServerSymbolInformationAsync(token: CancellationToken): Promise<SymbolInformation[]> {
while (this.isBusy) {
await sleep(5);
if (token.isCancellationRequested) {
return [];
}
}
return this._symbolInformations;
}

parse = (): void => {
parseAsync = async (token: CancellationToken): Promise<void> => {
this.isBusy = true;
console.log('Parsing document');
(new SyntaxParser()).parse(this);
console.log('Finished parsing document');
this.isBusy = false;
if (await (new SyntaxParser()).parseAsync(this, token)) {
this.isBusy = false;
}
};

registerNamedElementDeclaration(element: any) {
Expand Down Expand Up @@ -169,9 +157,20 @@ export abstract class BaseProjectDocument implements ProjectDocument {
* @returns a number for some reason.
*/
registerSymbolInformation = (element: HasSymbolInformation): number => {
console.debug(`Registering symbol for ${element.symbolInformation.name}`);
return this._symbolInformations.push(element.symbolInformation);
};

/** Get document information */
async getFoldingRanges(token: CancellationToken): Promise<FoldingRange[]> {
while (this.isBusy) {
await sleep(5);
if (token.isCancellationRequested) {
return [];
}
}
this.workspace.connection.console.info('Processing request for Folding Range');
return this._foldableElements;
}
}


Expand Down
1 change: 0 additions & 1 deletion server/src/project/elements/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export class ModuleElement extends BaseContextSyntaxElement implements HasSymbol
}

get symbolInformation(): SymbolInformation {
console.warn(`Creating symbol with name ${this._name}`);
return SymbolInformationFactory.create(
this, this.symbolKind
);
Expand Down
45 changes: 45 additions & 0 deletions server/src/project/parser/vbaSyntaxParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,54 @@ import { VbaClassDocument, VbaModuleDocument } from '../document';
import { FoldableElement } from '../elements/special';
import { ConstDeclarationsElement, EnumBlockDeclarationElement, EnumMemberDeclarationElement, MethodBlockDeclarationElement, VariableDeclarationsElement } from '../elements/memory';
import { ModuleElement } from '../elements/module';
import { sleep } from '../../utils/helpers';
import { CancellationToken } from 'vscode-languageserver';

export class SyntaxParser {
private static _lockIdentifier = 0;

private static _acquireLock(): number {
this._lockIdentifier += 1;
return this._lockIdentifier;
}

private static _hasLock(lockIdentifier: number): boolean {
return this._lockIdentifier === lockIdentifier;
}

private static _releaseLock(): void {
this._lockIdentifier = 0;
}

async parseAsync(document: VbaClassDocument | VbaModuleDocument, token: CancellationToken): Promise<boolean> {
// token.onCancellationRequested(e => {
// throw new Error("No");
// });

// Refuse to do anything that seems like too much work.
if (document.textDocument.lineCount > 1000) {
// TODO: Make this an option that people can increase or decrease.
console.log(`Document oversize: ${document.textDocument.lineCount} lines.`);
console.warn(`Syntax parsing has been disabled to prevent crashing.`);
return false;
}

// Wait a few seconds to see if any other input has ocurred.
const lock = SyntaxParser._acquireLock();
await sleep(1000);
if (!SyntaxParser._hasLock(lock)) {
console.info('Newer lock detected. Cancelling parse.');
return false;
}
SyntaxParser._releaseLock();

// Parse the document.
this.parse(document);
return true;
}

parse(document: VbaClassDocument | VbaModuleDocument) {
console.info('Parsing the document.');
const listener = new VbaTreeWalkListener(document);
const parser = this.createParser(document.textDocument);

Expand Down
50 changes: 22 additions & 28 deletions server/src/project/workspace.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CompletionItem, CompletionParams, DidChangeConfigurationNotification, DidChangeConfigurationParams, DidChangeWatchedFilesParams, DocumentSymbolParams, FoldingRange, FoldingRangeParams, Hover, HoverParams, SemanticTokensParams, SemanticTokensRangeParams, SymbolInformation, TextDocuments, WorkspaceFoldersChangeEvent, _Connection } from 'vscode-languageserver';
import { BaseProjectDocument, ProjectDocument } from './document';
import { CancellationToken, CancellationTokenSource, CompletionItem, CompletionParams, DidChangeConfigurationNotification, DidChangeConfigurationParams, DidChangeWatchedFilesParams, DocumentSymbolParams, FoldingRange, FoldingRangeParams, Hover, HoverParams, SemanticTokensParams, SemanticTokensRangeParams, SymbolInformation, TextDocuments, WorkspaceFoldersChangeEvent, _Connection } from 'vscode-languageserver';
import { BaseProjectDocument } from './document';
import { LanguageServerConfiguration } from '../server';
import { hasConfigurationCapability } from '../capabilities/workspaceFolder';
import { TextDocument } from 'vscode-languageserver-textdocument';
Expand All @@ -11,13 +11,13 @@ import { TextDocument } from 'vscode-languageserver-textdocument';
*/
export class Workspace {
private _events: WorkspaceEvents;
private _documents: ProjectDocument[] = [];
private _activeDocument?: ProjectDocument;
private _documents: BaseProjectDocument[] = [];
private _activeDocument?: BaseProjectDocument;
private _publicScopeDeclarations: Map<string, any> = new Map();

readonly connection: _Connection;

activateDocument(document: ProjectDocument) {
activateDocument(document: BaseProjectDocument) {
this._activeDocument = document;
}

Expand Down Expand Up @@ -59,8 +59,9 @@ class WorkspaceEvents {
private readonly _workspace: Workspace;
private readonly _documents: TextDocuments<TextDocument>;
private readonly _configuration: LanguageServerConfiguration;
private _parseCancellationToken?: CancellationTokenSource;

activeDocument?: ProjectDocument;
activeDocument?: BaseProjectDocument;

constructor(params: {connection: _Connection, workspace: Workspace, configuration: LanguageServerConfiguration}) {
this._workspace = params.workspace;
Expand All @@ -72,17 +73,16 @@ class WorkspaceEvents {
}

private initialiseConnectionEvents(connection: _Connection) {
console.log('Initialising connection events...');
connection.onInitialized(() => this.onInitialized());
connection.onCompletion(params => this.onCompletion(params));
connection.onCompletionResolve(item => this.onCompletionResolve(item));
connection.onDidChangeConfiguration(params => this.onDidChangeConfiguration(params));
connection.onDidChangeWatchedFiles(params => this.onDidChangeWatchedFiles(params));
connection.onDocumentSymbol((params) => this.onDocumentSymbolAsync(params));
connection.onDocumentSymbol(async (params, token) => await this.onDocumentSymbolAsync(params, token));
connection.onHover(params => this.onHover(params));

if (hasConfigurationCapability(this._configuration)) {
connection.onFoldingRanges((params) => this.onFoldingRanges(params));
connection.onFoldingRanges(async (params, token) => this.onFoldingRanges(params, token));
}

connection.onRequest((method: string, params: object | object[] | any) => {
Expand All @@ -101,19 +101,16 @@ class WorkspaceEvents {
}

private initialiseDocumentsEvents() {
console.log('Initialising documents events...');
this._documents.onDidChangeContent(e => this.onDidChangeContent(e.document));
this._documents.onDidChangeContent(async (e) => await this.onDidChangeContentAsync(e.document));
}

/** Connection event handlers */

private onCompletion(params: CompletionParams): never[] {
console.log(`onCompletion: ${params}`);
return [];
}

private onCompletionResolve(item: CompletionItem): CompletionItem {
console.log(`onCompletionResolve: ${item.label}`);
return item;
}

Expand All @@ -127,28 +124,22 @@ class WorkspaceEvents {

// TODO: Should trigger a full workspace refresh.
private onDidChangeWorkspaceFolders(e: WorkspaceFoldersChangeEvent) {
console.log(`onDidChangeWorkspaceFolders: ${e}`);
this._workspace.connection.console.log(`Workspace folder change event received.\n${e}`);
}

private async onDocumentSymbolAsync(params: DocumentSymbolParams): Promise<SymbolInformation[]> {
console.log(`onDocumentSymbolAsync: ${params.textDocument.uri}`);
return await this.activeDocument?.languageServerSymbolInformationAsync() ?? [];
private async onDocumentSymbolAsync(params: DocumentSymbolParams, token: CancellationToken): Promise<SymbolInformation[]> {
return await this.activeDocument?.languageServerSymbolInformationAsync(token) ?? [];
}

private onFoldingRanges(params: FoldingRangeParams): FoldingRange[] {
const foldingRanges = this._workspace.activeDocument?.foldableElements ?? [];
console.log(`onFoldingRanges: ${params.textDocument.uri} (${foldingRanges.length} ranges)`);
return foldingRanges;
private async onFoldingRanges(params: FoldingRangeParams, token: CancellationToken): Promise<FoldingRange[]> {
return await this._workspace.activeDocument?.getFoldingRanges(token) ?? [];
}

private onHover(params: HoverParams): Hover {
console.log(`onHover: ${params.position.line},${params.position.character}`);
return { contents: '' };
}

private onInitialized(): void {
console.log('onInitialized:---');
const connection = this._workspace.connection;
// Register for client configuration notification changes.
connection.client.register(DidChangeConfigurationNotification.type, undefined);
Expand All @@ -167,11 +158,14 @@ class WorkspaceEvents {
* This event handler is called whenever a `TextDocuments<TextDocument>` is changed.
* @param doc The document that changed.
*/
onDidChangeContent(doc: TextDocument) {
console.log('onDidChangeContent:--- ' + doc.uri);
async onDidChangeContentAsync(doc: TextDocument) {
// this._parseCancellationToken?.cancel();
// this._parseCancellationToken?.dispose();

this.activeDocument = BaseProjectDocument.create(this._workspace, doc);
this.activeDocument.parse();
this._parseCancellationToken = new CancellationTokenSource();
await this.activeDocument.parseAsync(this._parseCancellationToken.token);
this._parseCancellationToken = undefined;
this._workspace.activateDocument(this.activeDocument);
}

}
}
Loading