Skip to content

Commit ce5a9c3

Browse files
committed
Extension now supports some configuration.
1 parent b84400f commit ce5a9c3

File tree

5 files changed

+135
-100
lines changed

5 files changed

+135
-100
lines changed

package.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,26 @@
4848
},
4949
"configuration": {
5050
"type": "object",
51-
"title": "Default configuration",
51+
"title": "VBA Pro",
5252
"properties": {
53+
"vbaLanguageServer.maxDocumentLines": {
54+
"scope": "resource",
55+
"type": "number",
56+
"default": 1000,
57+
"description": "Limits the file size the language server will attempt to parse."
58+
},
5359
"vbaLanguageServer.maxNumberOfProblems": {
5460
"scope": "resource",
5561
"type": "number",
5662
"default": 100,
5763
"description": "Controls the maximum number of problems produced by the server."
5864
},
65+
"vbaLanguageServer.doWarnOptionExplicitMissing": {
66+
"scope": "resource",
67+
"type": "boolean",
68+
"default": true,
69+
"description": "The language server should warn when Option Explicit is not present."
70+
},
5971
"vbaLanguageServer.trace.server": {
6072
"scope": "window",
6173
"type": "string",
@@ -84,7 +96,7 @@
8496
]
8597
},
8698
"scripts": {
87-
"vscode:prepublish": "npm run compile",
99+
"vscode:prepublish": "npm run antlr4ng && npm run compile",
88100
"compile": "tsc -b",
89101
"watch": "tsc -b -w",
90102
"lint": "eslint ./client/src ./server/src --ext .ts,.tsx",

server/src/project/document.ts

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
import { CancellationToken, Diagnostic, PublishDiagnosticsParams, SymbolInformation, SymbolKind } from 'vscode-languageserver';
22
import { Workspace } from './workspace';
33
import { FoldableElement } from './elements/special';
4-
import { BaseSyntaxElement, HasDiagnosticCapability, HasSemanticToken, HasSymbolInformation, IdentifiableSyntaxElement, ScopeElement } from './elements/base';
4+
import { HasDiagnosticCapability, HasSemanticToken, HasSymbolInformation, IdentifiableSyntaxElement, ScopeElement } from './elements/base';
55
import { Range, TextDocument } from 'vscode-languageserver-textdocument';
66
import { SyntaxParser } from './parser/vbaSyntaxParser';
77
import { FoldingRange } from '../capabilities/folding';
88
import { SemanticTokensManager } from '../capabilities/semanticTokens';
99

10+
export interface DocumentSettings {
11+
maxDocumentLines: number;
12+
maxNumberOfProblems: number;
13+
doWarnOptionExplicitMissing: boolean;
14+
}
1015

1116
export abstract class BaseProjectDocument {
1217
readonly name: string;
1318
readonly workspace: Workspace;
1419
readonly textDocument: TextDocument;
20+
protected _documentConfiguration?: DocumentSettings;
1521

1622
protected _hasDiagnosticElements: HasDiagnosticCapability[] = [];
1723
protected _unhandledNamedElements: [] = [];
@@ -26,16 +32,53 @@ export abstract class BaseProjectDocument {
2632
protected _semanticTokens: SemanticTokensManager = new SemanticTokensManager();
2733

2834
protected _isBusy = true;
35+
// protected _hasParseResult = false;
2936
abstract symbolKind: SymbolKind
3037

31-
get Busy() {
38+
get isBusy() {
3239
return this._isBusy;
3340
}
3441

42+
get isOversize() {
43+
// Workaround for async getter.
44+
return (async () =>
45+
this.textDocument.lineCount > (await this.getDocumentConfiguration()).maxDocumentLines
46+
)();
47+
}
48+
49+
// get hasParseResult() {
50+
// return this._hasParseResult;
51+
// }
52+
3553
get currentScopeElement() {
3654
return this._elementParents.at(-1);
3755
}
3856

57+
async getDocumentConfiguration(): Promise<DocumentSettings> {
58+
// Get the stored configuration.
59+
if (this._documentConfiguration) {
60+
return this._documentConfiguration;
61+
}
62+
63+
// Get the configuration from the client.
64+
if (this.workspace.hasConfigurationCapability) {
65+
this._documentConfiguration = await this.workspace.requestDocumentSettings(this.textDocument.uri);
66+
if (this._documentConfiguration) {
67+
return this._documentConfiguration;
68+
}
69+
}
70+
71+
// Use the defaults.
72+
this._documentConfiguration = {
73+
maxDocumentLines: 1500,
74+
maxNumberOfProblems: 100,
75+
doWarnOptionExplicitMissing: true,
76+
};
77+
return this._documentConfiguration;
78+
}
79+
80+
clearDocumentConfiguration = () => this._documentConfiguration = undefined;
81+
3982
// get activeAttributeElement() {
4083
// return this._attributeElements?.at(-1);
4184
// }
@@ -90,20 +133,19 @@ export abstract class BaseProjectDocument {
90133
.map((e) => e.diagnostics).flat(1) };
91134
}
92135

93-
parseAsync = async (token: CancellationToken): Promise<void> => {
94-
if (!this._isBusy) {
95-
console.log("Parser busy!");
96-
console.log(`v${this.textDocument.version}: ${this.textDocument.uri}`);
97-
this._isBusy = true;
98-
}
99-
if (await (new SyntaxParser()).parseAsync(this, token)) {
100-
console.log("Parser idle!");
136+
async parseAsync(token: CancellationToken): Promise<void> {
137+
if (await this.isOversize) {
138+
console.log(`Document oversize: ${this.textDocument.lineCount} lines.`);
139+
console.warn(`Syntax parsing has been disabled to prevent crashing.`);
101140
this._isBusy = false;
141+
return;
102142
}
143+
await (new SyntaxParser()).parseAsync(this, token)
103144
this._hasDiagnosticElements.forEach(element => {
104145
element.evaluateDiagnostics;
105146
this._diagnostics.concat(element.diagnostics);
106147
});
148+
this._isBusy = false;
107149
};
108150

109151
registerNamedElementDeclaration(element: any) {

server/src/project/elements/module.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,22 @@ import { IgnoredAttributeDiagnostic, MissingAttributeDiagnostic, MissingOptionEx
77
import '../../extensions/stringExtensions';
88
import { ScopeElement } from './special';
99

10+
interface DocumentSettings {
11+
doWarnOptionExplicitMissing: boolean;
12+
}
1013

1114
abstract class BaseModuleElement extends ScopeElement implements HasSymbolInformation, HasDiagnosticCapability {
1215
protected abstract _name: string;
1316
symbolKind: SymbolKind;
1417
diagnostics: Diagnostic[] = [];
1518
context: ProceduralModuleContext | ClassModuleContext;
19+
settings: DocumentSettings;
1620

17-
constructor(context: ProceduralModuleContext | ClassModuleContext, document: TextDocument, symbolKind: SymbolKind) {
21+
constructor(context: ProceduralModuleContext | ClassModuleContext, document: TextDocument, symbolKind: SymbolKind, documentSettings: DocumentSettings) {
1822
super(context, document);
1923
this.context = context;
2024
this.symbolKind = symbolKind;
25+
this.settings = documentSettings;
2126
}
2227

2328
get name(): string {
@@ -63,14 +68,14 @@ export class ModuleElement extends BaseModuleElement {
6368
context: ProceduralModuleContext;
6469
protected _name: string;
6570

66-
constructor(context: ProceduralModuleContext, document: TextDocument) {
67-
super(context, document, SymbolKind.File);
71+
constructor(context: ProceduralModuleContext, document: TextDocument, documentSettings: DocumentSettings) {
72+
super(context, document, SymbolKind.File, documentSettings);
6873
this.context = context;
6974
this._name = this._getName(context);
7075
}
7176

7277
evaluateDiagnostics(): void {
73-
if (!this._hasOptionExplicit) {
78+
if (this.settings.doWarnOptionExplicitMissing && !this._hasOptionExplicit) {
7479
const header = this.context.proceduralModuleHeader();
7580
const startLine = header.stop?.line ?? 0 + 1;
7681
this.diagnostics.push(new MissingOptionExplicitDiagnostic(
@@ -101,14 +106,14 @@ export class ClassElement extends BaseModuleElement {
101106
context: ClassModuleContext;
102107
protected _name: string;
103108

104-
constructor(context: ClassModuleContext, document: TextDocument) {
105-
super(context, document, SymbolKind.Class);
109+
constructor(context: ClassModuleContext, document: TextDocument, documentSettings: DocumentSettings) {
110+
super(context, document, SymbolKind.Class, documentSettings);
106111
this.context = context;
107112
this._name = this._getName(context);
108113
}
109114

110115
evaluateDiagnostics(): void {
111-
if (!this._hasOptionExplicit) {
116+
if (this.settings.doWarnOptionExplicitMissing && !this._hasOptionExplicit) {
112117
const header = this.context.classModuleHeader();
113118
const startLine = header.stop?.line ?? 0 + 1;
114119
this.diagnostics.push(new MissingOptionExplicitDiagnostic(

server/src/project/parser/vbaSyntaxParser.ts

Lines changed: 13 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,65 +4,25 @@ import { vbaLexer } from '../../antlr/out/vbaLexer';
44
import { ClassModuleContext, ConstItemContext, EnumDeclarationContext, IgnoredAttrContext, ProceduralModuleContext, ProcedureDeclarationContext, UdtDeclarationContext, WhileStatementContext, vbaParser } from '../../antlr/out/vbaParser';
55
import { vbaListener } from '../../antlr/out/vbaListener';
66

7-
import { VbaClassDocument, VbaModuleDocument } from '../document';
8-
import { sleep } from '../../utils/helpers';
7+
import { DocumentSettings, VbaClassDocument, VbaModuleDocument } from '../document';
98
import { CancellationToken } from 'vscode-languageserver';
109
import { CharStream, CommonTokenStream, DefaultErrorStrategy, ErrorNode, ParseTreeWalker, Parser, RecognitionException } from 'antlr4ng';
1110
import { ClassElement, IgnoredAttributeElement, ModuleElement } from '../elements/module';
1211
import { ConstDeclarationElement, DeclarationElement, EnumDeclarationElement, TypeDeclarationElement } from '../elements/memory';
1312
import { WhileLoopElement } from '../elements/flow';
1413

1514
export class SyntaxParser {
16-
private static _lockIdentifier = 0;
17-
18-
private static _acquireLock(): number {
19-
this._lockIdentifier += 1;
20-
return this._lockIdentifier;
21-
}
22-
23-
private static _hasLock(lockIdentifier: number): boolean {
24-
return this._lockIdentifier === lockIdentifier;
25-
}
26-
27-
private static _releaseLock(): void {
28-
this._lockIdentifier = 0;
29-
}
30-
3115
async parseAsync(document: VbaClassDocument | VbaModuleDocument, token: CancellationToken): Promise<boolean> {
32-
// token.onCancellationRequested(e => {
33-
// throw new Error("No");
34-
// });
35-
36-
// Refuse to do anything that seems like too much work.
37-
if (document.textDocument.lineCount > 2000) {
38-
// TODO: Make this an option that people can increase or decrease.
39-
console.log(`Document oversize: ${document.textDocument.lineCount} lines.`);
40-
console.warn(`Syntax parsing has been disabled to prevent crashing.`);
41-
return false;
42-
}
43-
44-
// Wait a few seconds to see if any other input has ocurred.
45-
const lock = SyntaxParser._acquireLock();
46-
await sleep(1000);
47-
if (!SyntaxParser._hasLock(lock)) {
48-
console.info('Newer lock detected. Cancelling parse.');
49-
return false;
50-
}
51-
SyntaxParser._releaseLock();
52-
53-
// Parse the document.
54-
this.parse(document);
55-
return true;
56-
}
57-
58-
parse(document: VbaClassDocument | VbaModuleDocument) {
59-
console.info('Parsing the document.');
16+
console.debug(`Parse requested: ${document.textDocument.version}`);
6017
const listener = new VbaListener(document);
18+
await listener.ensureHasSettings();
6119
const parser = this.createParser(document.textDocument);
6220
ParseTreeWalker.DEFAULT.walk(
6321
listener,
64-
parser.startRule()
22+
parser.startRule(),
23+
token
6524
);
25+
return true;
6626
}
6727

6828
private createParser(doc: TextDocument): VbaParser {
@@ -87,13 +47,18 @@ class VbaParser extends vbaParser {
8747

8848
class VbaListener extends vbaListener {
8949
document: VbaClassDocument | VbaModuleDocument;
50+
protected _documentSettings?: DocumentSettings;
9051
protected _isAfterMethodDeclaration = false;
9152

9253
constructor(document: VbaClassDocument | VbaModuleDocument) {
9354
super();
9455
this.document = document;
9556
}
9657

58+
async ensureHasSettings() {
59+
this._documentSettings = await this.document.getDocumentConfiguration();
60+
}
61+
9762
enterEnumDeclaration = (ctx: EnumDeclarationContext) => {
9863
const element = new EnumDeclarationElement(ctx, this.document.textDocument, this._isAfterMethodDeclaration);
9964
this.document.registerFoldableElement(element)
@@ -113,7 +78,7 @@ class VbaListener extends vbaListener {
11378
};
11479

11580
enterClassModule = (ctx: ClassModuleContext) => {
116-
const element = new ClassElement(ctx, this.document.textDocument);
81+
const element = new ClassElement(ctx, this.document.textDocument, this._documentSettings ?? {doWarnOptionExplicitMissing: true});
11782
this.document.registerSymbolInformation(element)
11883
.registerDiagnosticElement(element)
11984
.registerScopedElement(element);
@@ -135,7 +100,7 @@ class VbaListener extends vbaListener {
135100
};
136101

137102
enterProceduralModule = (ctx: ProceduralModuleContext) => {
138-
const element = new ModuleElement(ctx, this.document.textDocument);
103+
const element = new ModuleElement(ctx, this.document.textDocument, this._documentSettings ?? {doWarnOptionExplicitMissing: true});
139104
this.document.registerSymbolInformation(element)
140105
.registerDiagnosticElement(element)
141106
.registerScopedElement(element);

0 commit comments

Comments
 (0)