Skip to content

Commit 5d6b12c

Browse files
committed
simple multi line errors
1 parent 4460d3c commit 5d6b12c

File tree

4 files changed

+143
-44
lines changed

4 files changed

+143
-44
lines changed

src/features/constraintMenu/AutoCompletion.ts

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,17 @@ export interface RequiredCompletionParts {
88

99
export interface ValidationError {
1010
message: string;
11+
line: number;
1112
startColumn: number;
1213
endColumn: number;
1314
}
1415

16+
interface Token {
17+
text: string;
18+
line: number;
19+
column: number;
20+
}
21+
1522
export type WordCompletion = RequiredCompletionParts & Partial<monaco.languages.CompletionItem>;
1623

1724
export interface AbstractWord {
@@ -89,35 +96,66 @@ export class NegatableWord implements AbstractWord {
8996
}
9097

9198
export class AutoCompleteTree {
92-
private content: string[];
93-
/** value matches the start column of the value at the same index in content */
94-
private startColumns: number[];
95-
private length: number;
99+
private content: Token[];
96100

97101
constructor(private roots: AutoCompleteNode[]) {
98102
this.content = [];
99-
this.startColumns = [];
100-
this.length = 0;
101103
}
102104

103105
/**
104106
* Sets the content of the tree for the next analyzing cycle
105107
*/
106-
private setContent(line: string) {
107-
if (!line) {
108-
line = "";
108+
private setContent(text: string) {
109+
if (!text) {
110+
text = "";
109111
}
110-
if (line.length == 0) {
112+
if (text.length == 0) {
111113
this.content = [];
112-
this.length = 0;
113114
return;
114115
}
115-
this.content = line.split(" ");
116-
this.startColumns = this.content.map(() => 0);
117-
for (let i = 1; i < this.content.length; i++) {
118-
this.startColumns[i] = this.startColumns[i - 1] + this.content[i - 1].length + 1;
116+
117+
let currentToken = "";
118+
let currentLine = 1;
119+
let currentColumn = 0;
120+
this.content = [];
121+
let index = 0;
122+
while (index < text.length) {
123+
const char = text[index];
124+
if (char === "\n") {
125+
if (currentToken.length > 0) {
126+
this.content.push({
127+
text: currentToken,
128+
line: currentLine,
129+
column: currentColumn - currentToken.length + 1,
130+
});
131+
}
132+
currentToken = "";
133+
currentLine++;
134+
currentColumn = 1;
135+
} else if (char === " " || char === "\t") {
136+
if (currentToken.length > 0) {
137+
this.content.push({
138+
text: currentToken,
139+
line: currentLine,
140+
column: currentColumn - currentToken.length + 1,
141+
});
142+
}
143+
currentToken = "";
144+
currentColumn += 1;
145+
} else {
146+
currentToken += char;
147+
currentColumn += 1;
148+
}
149+
index++;
150+
}
151+
if (currentToken.length > 0) {
152+
this.content.push({
153+
text: currentToken,
154+
line: currentLine,
155+
column: currentColumn - currentToken.length + 1,
156+
});
119157
}
120-
this.length = line.length;
158+
this.content = this.content.map((c) => ({ ...c, text: c.text.trim() })).filter((c) => c.text.length > 0);
121159
}
122160

123161
/**
@@ -130,23 +168,37 @@ export class AutoCompleteTree {
130168
}
131169

132170
private verifyNode(nodes: AutoCompleteNode[], index: number, comesFromFinal: boolean): ValidationError[] {
171+
if (comesFromFinal && this.content[index].column == 0) {
172+
const checkStart = this.verifyNode(this.roots, index, true);
173+
if (checkStart.length > 0) {
174+
return checkStart;
175+
}
176+
}
133177
if (index >= this.content.length) {
134178
if (nodes.length == 0 || comesFromFinal) {
135179
return [];
136180
} else {
137-
return [{ message: "Unexpected end of line", startColumn: this.length - 1, endColumn: this.length }];
181+
return [
182+
{
183+
message: "Unexpected end of line",
184+
line: this.content[index - 1].line,
185+
startColumn: this.content[index - 1].column + this.content[index - 1].text.length - 1,
186+
endColumn: this.content[index - 1].column + this.content[index - 1].text.length,
187+
},
188+
];
138189
}
139190
}
140191

141192
const foundErrors: ValidationError[] = [];
142193
let childErrors: ValidationError[] = [];
143194
for (const n of nodes) {
144-
const v = n.word.verifyWord(this.content[index]);
195+
const v = n.word.verifyWord(this.content[index].text);
145196
if (v.length > 0) {
146197
foundErrors.push({
147198
message: v[0],
148-
startColumn: this.startColumns[index],
149-
endColumn: this.startColumns[index] + this.content[index].length,
199+
startColumn: this.content[index].column,
200+
endColumn: this.content[index].column + this.content[index].text.length,
201+
line: this.content[index].line,
150202
});
151203
continue;
152204
}
@@ -167,7 +219,7 @@ export class AutoCompleteTree {
167219
/**
168220
* Calculates the completion options for the current content
169221
*/
170-
public getCompletion(line: string, lineNumber = 1): monaco.languages.CompletionItem[] {
222+
public getCompletion(line: string): monaco.languages.CompletionItem[] {
171223
this.setContent(line);
172224
let result: WordCompletion[] = [];
173225
if (this.content.length == 0) {
@@ -177,46 +229,52 @@ export class AutoCompleteTree {
177229
} else {
178230
result = this.completeNode(this.roots, 0);
179231
}
180-
return this.transformResults(result, lineNumber);
232+
return this.transformResults(result);
181233
}
182234

183235
private completeNode(nodes: AutoCompleteNode[], index: number): WordCompletion[] {
184236
let result: WordCompletion[] = [];
185237
if (index == this.content.length - 1) {
186238
for (const node of nodes) {
187-
result = result.concat(node.word.completionOptions(this.content[index]));
239+
result = result.concat(node.word.completionOptions(this.content[index].text));
188240
}
189241
return result;
190242
}
191243
for (const n of nodes) {
192-
if (!n.word.verifyWord(this.content[index])) {
244+
if (!n.word.verifyWord(this.content[index].text)) {
193245
continue;
194246
}
195247
result = result.concat(this.completeNode(n.children, index + 1));
196248
}
197249
return result;
198250
}
199251

200-
private transformResults(comp: WordCompletion[], lineNumber = 1): monaco.languages.CompletionItem[] {
252+
private transformResults(comp: WordCompletion[]): monaco.languages.CompletionItem[] {
201253
const result: monaco.languages.CompletionItem[] = [];
202254
const filtered = comp.filter(
203255
(c, idx) => comp.findIndex((c2) => c2.insertText === c.insertText && c2.kind === c.kind) === idx,
204256
);
205257
for (const c of filtered) {
206-
const r = this.transformResult(c, lineNumber);
258+
const r = this.transformResult(c);
207259
result.push(r);
208260
}
209261
return result;
210262
}
211263

212-
private transformResult(comp: WordCompletion, lineNumber = 1): monaco.languages.CompletionItem {
213-
const wordStart = this.content.length == 0 ? 1 : this.length - this.content[this.content.length - 1].length + 1;
264+
private transformResult(comp: WordCompletion): monaco.languages.CompletionItem {
265+
const wordStart = this.content.length == 0 ? 1 : this.content[this.content.length - 1].column - 1;
266+
const lineNumber = this.content.length == 0 ? 1 : this.content[this.content.length - 1].line;
214267
return {
215268
insertText: comp.insertText,
216269
kind: comp.kind,
217270
label: comp.label ?? comp.insertText,
218271
insertTextRules: comp.insertTextRules,
219-
range: new monaco.Range(lineNumber, wordStart + (comp.startOffset ?? 0), lineNumber, this.length + 1),
272+
range: new monaco.Range(
273+
lineNumber,
274+
wordStart + (comp.startOffset ?? 0),
275+
lineNumber,
276+
wordStart + (comp.startOffset ?? 0) + comp.insertText.length,
277+
),
220278
};
221279
}
222280
}

src/features/constraintMenu/ConstraintMenu.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,23 @@ export class ConstraintMenu extends AbstractUIExtension implements Switchable {
142142
const marker: monaco.editor.IMarkerData[] = [];
143143
// empty content gets accepted as valid as it represents no constraints
144144
if (content !== "") {
145-
const lines = this.editor.getValue().split(/\r?\n/gm);
146-
const errors = lines.map(this.tree.verify, this.tree);
145+
const constraintTexts = ConstraintRegistry.splitToConstraintTexts(content);
146+
const errors = constraintTexts.map(
147+
(c) => ({
148+
errors: this.tree.verify(c.text),
149+
c,
150+
}),
151+
this.tree,
152+
);
147153
for (let i = 0; i < errors.length; i++) {
148154
const lineErrors = errors[i];
149155
marker.push(
150-
...lineErrors.map((e) => ({
156+
...lineErrors.errors.map((e) => ({
151157
severity: monaco.MarkerSeverity.Error,
152-
startLineNumber: i + 1,
153-
startColumn: e.startColumn + 1,
154-
endLineNumber: i + 1,
155-
endColumn: e.endColumn + 1,
158+
startLineNumber: e.line + lineErrors.c.line,
159+
startColumn: e.startColumn,
160+
endLineNumber: e.line + lineErrors.c.line,
161+
endColumn: e.endColumn,
156162
message: e.message,
157163
})),
158164
);

src/features/constraintMenu/DslLanguage.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,20 @@ export class MonacoEditorConstraintDslCompletionProvider implements monaco.langu
2424
model: monaco.editor.ITextModel,
2525
position: monaco.Position,
2626
): monaco.languages.ProviderResult<monaco.languages.CompletionList> {
27-
const r = this.tree.getCompletion(
28-
model.getLineContent(position.lineNumber).substring(0, position.column - 1),
29-
position.lineNumber,
30-
);
27+
const lines = model.getLinesContent();
28+
let curText = "";
29+
if (lines[position.lineNumber - 1].startsWith("-")) {
30+
curText = lines[position.lineNumber - 1].substring(1, position.column - 1);
31+
} else {
32+
curText = lines[position.lineNumber - 1].substring(0, position.column - 1);
33+
for (let i = position.lineNumber - 2; i >= 0; i--) {
34+
curText = lines[i] + "\n" + curText;
35+
if (lines[i].startsWith("-")) {
36+
break;
37+
}
38+
}
39+
}
40+
const r = this.tree.getCompletion(model.getLineContent(position.lineNumber).substring(0, position.column - 1));
3141
return {
3242
suggestions: r,
3343
};

src/features/constraintMenu/constraintRegistry.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ export class ConstraintRegistry {
1313
private updateCallbacks: (() => void)[] = [];
1414

1515
public setConstraints(constraints: string): void {
16-
this.constraints = constraints.split("\r?\n").map(this.constraintFromLine);
17-
this.constraintListChanged();
16+
this.constraints = ConstraintRegistry.splitToConstraintTexts(constraints).map((t) =>
17+
this.constraintFromText(t.text),
18+
);
19+
//this.constraintListChanged();
1820
}
1921

2022
public setConstraintsFromArray(constraints: Constraint[]): void {
@@ -26,8 +28,8 @@ export class ConstraintRegistry {
2628
this.constraintListChanged();
2729
}
2830

29-
private constraintFromLine(line: string): Constraint {
30-
const parts = line.split(" ");
31+
private constraintFromText(text: string): Constraint {
32+
const parts = text.split(" ");
3133
if (parts.length < 2) {
3234
return {
3335
id: generateRandomSprottyId(),
@@ -70,4 +72,27 @@ export class ConstraintRegistry {
7072
public getConstraintList(): Constraint[] {
7173
return this.constraints;
7274
}
75+
76+
public static splitToConstraintTexts(text: string): { text: string; line: number }[] {
77+
if (text === "") {
78+
return [];
79+
}
80+
const lines = text.split(/\r?\n/gm);
81+
let currentConstraint = "";
82+
const constraints: { text: string; line: number }[] = [];
83+
let lastStart = 0;
84+
for (let i = 0; i < lines.length; i++) {
85+
const line = lines[i].trim();
86+
if (line.startsWith("-")) {
87+
if (currentConstraint) {
88+
constraints.push({ text: currentConstraint, line: lastStart });
89+
}
90+
lastStart = i;
91+
currentConstraint = line;
92+
} else {
93+
currentConstraint += `\n${line}`;
94+
}
95+
}
96+
return currentConstraint ? [...constraints, { text: currentConstraint, line: lastStart }] : constraints;
97+
}
7398
}

0 commit comments

Comments
 (0)