Skip to content

Commit e2aedd7

Browse files
authored
DCL Auto-Completion Provider (#203)
DCL Auto-Completion Provider (#203)
1 parent efcad0e commit e2aedd7

22 files changed

+1571
-52
lines changed

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
node_modules
2-
out
1+
/node_modules
2+
/out
33
vendor
44
tmp
55
autolispext*.zip

extension/smartBracket/language-configuration-dcl.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,9 @@
1717
"start": "^\\s*\/\/\\s*#?region\\b",
1818
"end": "^\\s*\/\/\\s*#?endregion\\b"
1919
}
20-
}
20+
},
21+
"indentationRules": {
22+
"increaseIndentPattern": "{",
23+
"decreaseIndentPattern": "}"
24+
}
2125
}

extension/src/astObjects/dclAtom.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,33 @@ export class DclAtom implements IDclFragment {
3434
return /^".*"$/.test(this.symbol);
3535
}
3636

37+
get isNumber(): boolean {
38+
return /^\d+\.*\d*$/.test(this.symbol);
39+
}
40+
3741
get range(): Range {
38-
return new Range(this.line, this.column, this.line, this.column + this.symbol.length);
42+
if (!this.symbol.includes('\n')) {
43+
return new Range(this.line, this.column, this.line, this.column + this.symbol.length);
44+
}
45+
46+
let cLine = this.line;
47+
let cColm = this.column;
48+
const begin: Position = new Position(cLine, cColm);
49+
for (let i = 0; i < this.symbol.length; i++) {
50+
const ch = this.symbol[i];
51+
if (ch === '\n') {
52+
cLine += 1;
53+
cColm = 0;
54+
} else {
55+
cColm += 1;
56+
}
57+
}
58+
const close: Position = new Position(cLine, cColm);
59+
return new Range(begin.line, begin.character, close.line, close.character);
60+
}
61+
62+
get rank(): number {
63+
return this.line * 1000 + this.column;
3964
}
4065

4166
equal(atom: IDclFragment): boolean {

extension/src/astObjects/dclAttribute.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,18 @@ export class DclAttribute implements IDclContainer {
3232
return false;
3333
}
3434

35+
get isNumber(): boolean {
36+
return false;
37+
}
38+
3539
get range(): Range {
3640
const lastAtom = this.atoms[this.length - 1];
3741
return new Range(this.firstAtom.range.start, lastAtom.range.end);
3842
}
43+
44+
get rank(): number {
45+
return this.line * 1000 + this.column;
46+
}
3947

4048
equal(dclObject: IDclFragment): boolean {
4149
return JSON.stringify(this) === JSON.stringify(dclObject);
@@ -83,6 +91,10 @@ export class DclAttribute implements IDclContainer {
8391
return null;
8492
}
8593

94+
getImpliedParent(position: Position): IDclContainer {
95+
return this.contains(position) ? this : null;
96+
}
97+
8698
flatten(into?: Array<DclAtom>): Array<DclAtom> {
8799
if (!into) {
88100
into = [];
@@ -112,15 +124,12 @@ export class DclAttribute implements IDclContainer {
112124
return this.length < 3 ? null : this.atoms[2];
113125
}
114126

115-
// Context: DCL Attributes are valid in 2 arrangements: "Key = Value;" OR "key;"
116-
// Any other arrangement should be considered a malformed syntax error.
127+
117128
get isWellFormed(): boolean {
118-
const invalid = this.length < 2 // probably has key, but missing semi-colon
119-
|| this.length === 3 // probably missing equal or semi-colon
120-
|| this.length > 4 // exceeds possible key-value-pair structure
121-
|| this.lastAtom.symbol !== ';' // invalid attribute termination
122-
|| this.firstAtom.isString // strings are invalid keys
123-
|| (this.length === 4 && this.atoms[1].symbol !== '=');
129+
const invalid = this.length !== 4 // exceeds possible key-value-pair structure
130+
|| this.lastAtom.symbol !== ';' // invalid attribute termination
131+
|| this.firstAtom.isString // strings are invalid keys
132+
|| this.atoms[1].symbol !== '='; // Unexpected delineator
124133
return !invalid;
125134
}
126135

extension/src/astObjects/dclInterfaces.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@ export interface IDclFragment {
77
readonly line: number;
88
readonly column: number;
99
readonly symbol?: string | undefined;
10-
readonly flatIndex?: number | undefined;
10+
readonly flatIndex?: number | undefined; // This is ideal for comparing an ASTObjects position to another ASTObject
1111
readonly asTile?: DclTile | undefined;
1212
readonly asAttribute?: DclAttribute | undefined;
1313
readonly asContainer?: IDclContainer | undefined;
1414
readonly isComment: boolean;
1515
readonly isBlockComment: boolean;
1616
readonly isString: boolean;
17+
readonly isNumber: boolean;
1718
readonly range: Range;
19+
readonly rank: number; // This is ideal for comparing cursor position to an ASTObject
1820

1921
equal(atom: IDclFragment): boolean;
2022
contains(position: Position): boolean;
@@ -27,6 +29,8 @@ export interface IDclContainer extends IDclFragment {
2729
readonly firstAtom: IDclFragment;
2830
readonly lastAtom: IDclFragment;
2931
readonly firstNonComment: IDclFragment;
32+
readonly isWellFormed: boolean;
3033
getParentFrom(position: Position|IDclFragment, tilesOnly: boolean): IDclContainer;
34+
getImpliedParent(position: Position): IDclContainer;
3135
flatten(into?: Array<DclAtom>): Array<DclAtom>;
3236
}

extension/src/astObjects/dclTile.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,20 @@ export class DclTile implements IDclContainer {
3434
return false;
3535
}
3636

37+
get isNumber(): boolean {
38+
return false;
39+
}
40+
3741
get range(): Range {
3842
const begin = this.firstAtom.range.start;
3943
const close = this.lastAtom.range.end;
4044
return new Range(begin, close);
4145
}
4246

47+
get rank(): number {
48+
return this.line * 1000 + this.column;
49+
}
50+
4351
equal(atom: IDclFragment): boolean {
4452
return JSON.stringify(this) === JSON.stringify(atom);
4553
}
@@ -88,12 +96,27 @@ export class DclTile implements IDclContainer {
8896
return null;
8997
}
9098

99+
get isWellFormed(): boolean {
100+
if (this.length === 2) {
101+
return /^[a-z\_]+$/i.test(this.firstAtom.symbol) && this.lastAtom.symbol === ';';
102+
}
103+
104+
const name = this.tileTypeAtom?.symbol.toLowerCase() ?? '';
105+
106+
return this.firstAtom?.symbol === ':'
107+
&& this.closeBracketAtom?.symbol === '}'
108+
&& /^[a-z\_]+$/i.test(name)
109+
&& this.openBracketAtom?.symbol === '{'; // doing this one last because it loops over all atoms
110+
}
111+
91112
getParentFrom(position: Position|IDclFragment, tilesOnly = false): IDclContainer {
92113
const pos = position instanceof Position ? position : position.range.start;
93114
if (this.contains(pos)) {
115+
let failed = 0;
94116
for (let i = 0; i < this.length; i++) {
95117
const dclObj = this.atoms[i];
96118
if (!dclObj.contains(pos)) {
119+
failed++;
97120
continue;
98121
}
99122
if (dclObj instanceof DclAttribute) {
@@ -104,7 +127,40 @@ export class DclTile implements IDclContainer {
104127
return dclObj.asTile.getParentFrom(pos, tilesOnly) ?? this;
105128
}
106129
}
130+
if (failed === this.length) {
131+
return this; // position is not at the location of an atom, but we only care about the parent anyway
132+
}
133+
}
134+
return null;
135+
}
136+
137+
getImpliedParent(position: Position): IDclContainer {
138+
// Note: attributes handle themselves, your challange is determining if an attribute is malformed and needs a force return.
139+
let prev = 0;
140+
const positionRank = position.line * 1000 + position.character;
141+
for (let i = 0; i < this.atoms.length; i++) {
142+
const atom = this.atoms[i];
143+
if (prev > 0 && positionRank > prev && positionRank < atom.rank) {
144+
return this.atoms[i-1].asContainer ?? this;
145+
}
146+
147+
const contains = atom.range.contains(position);
148+
if (atom instanceof DclAtom) {
149+
if (contains) {
150+
return this;
151+
}
152+
prev = atom.rank;
153+
continue;
154+
}
155+
156+
if (contains) {
157+
// if the container is an attribute it will return itself, if its a tile it will keep digging for the best scenario
158+
return atom.asContainer.getImpliedParent(position) ?? this;
159+
}
160+
161+
prev = atom.rank;
107162
}
163+
108164
return null;
109165
}
110166

extension/src/commands.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
import * as vscode from "vscode";
2+
import {
3+
TextDocument, ProviderResult, CancellationToken,
4+
Position, Range,
5+
CompletionContext, CompletionItem, CompletionList
6+
} from "vscode";
7+
28
import * as nls from 'vscode-nls';
39
import { AutoLispExt } from './context';
410
import { generateDocumentationSnippet, getDefunArguments, getDefunAtPosition } from './help/userDocumentation';
@@ -9,6 +15,7 @@ import { AutoLispExtPrepareRename, AutoLispExtProvideRenameEdits } from './provi
915
import { SymbolManager } from './symbols';
1016
import {AutoLispExtProvideHover} from "./providers/hoverProvider";
1117
import { DocumentServices } from './services/documentServices';
18+
import { invokeCompletionProviderDcl } from './completion/completionProviderDcl';
1219

1320
const localize = nls.loadMessageBundle();
1421

@@ -178,4 +185,13 @@ export function registerCommands(context: vscode.ExtensionContext){
178185
}
179186
}));
180187

188+
AutoLispExt.Subscriptions.push(vscode.languages.registerCompletionItemProvider([DocumentServices.Selectors.DCL], {
189+
190+
provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken, context: CompletionContext) : ProviderResult<CompletionItem[]|CompletionList> {
191+
const roDoc = AutoLispExt.Documents.getDocument(document);
192+
return invokeCompletionProviderDcl(roDoc, position, context);
193+
}
194+
}, ...[' ', ':', '=', ';', '/']));
195+
196+
181197
}

0 commit comments

Comments
 (0)