Skip to content

Commit 2bf1d41

Browse files
committed
added support for prepending lines
1 parent 4b033f7 commit 2bf1d41

File tree

3 files changed

+51
-27
lines changed

3 files changed

+51
-27
lines changed

server/src/ast.ts

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@ export class AST {
1616
[module_name: string]: Map<string, { range: Range, local: string }>
1717
} = {};
1818
private uri: string;
19+
private prependLines: number;
1920
private diagnostics: Diagnostic[] = [];
2021
// Array of callbacks to call once parsing is done
2122
// Needed for things like checking if an variable name has already been declared
2223
private diagnosticsCallbacks: Array<[(child: Node) => void, Node]> = [];
2324

24-
constructor(text: string, context: Context, uri: string) {
25+
// If prepend is supplied, prepend it to text, and offset all locations returned back to client
26+
constructor(text: string, context: Context, uri: string, prependLines: number = 0) {
27+
this.prependLines = prependLines;
2528
const acornOptions: Options = {
2629
ecmaVersion: DEFAULT_ECMA_VERSION,
2730
sourceType: "module",
@@ -327,15 +330,17 @@ export class AST {
327330
else return undefined;
328331
}
329332

330-
public getOccurences(pos: Position): DocumentHighlight[] {
331-
const identifier = this.findIdentifierNode(vsPosToEsPos(pos));
333+
public getOccurences(pos: Position, identifier?: Identifier, declaration?: DeclarationSymbol): Range[] {
334+
if (identifier === undefined)
335+
identifier = this.findIdentifierNode(vsPosToEsPos(pos));
332336
if (!identifier) return [];
333337

334-
const declaration = this.findDeclarationByName(identifier.name, identifier.loc!);
338+
if (declaration === undefined)
339+
declaration = this.findDeclarationByName(identifier.name, identifier.loc!);
335340
if (!declaration) return [];
336341

337342
let scopeFound = false;
338-
const ret: DocumentHighlight[] = [];
343+
const ret: Range[] = [];
339344
const queue: Node[] = [this.ast];
340345

341346
while (queue.length > 0) {
@@ -345,7 +350,7 @@ export class AST {
345350
if (!scopeFound && node.loc && sourceLocEquals(node.loc, declaration.scope))
346351
scopeFound = true;
347352
if (scopeFound && node.type === NODES.IDENTIFIER && node.name === identifier.name)
348-
ret.push({ range: sourceLocToRange(node.loc!) })
353+
ret.push(sourceLocToRange(node.loc!))
349354

350355
getNodeChildren(node, true).forEach(node => {
351356
if (
@@ -364,7 +369,6 @@ export class AST {
364369
}
365370

366371
public getDocumentSymbols(): DocumentSymbol[] {
367-
368372
let ret: DocumentSymbol[] = []
369373
this.declarations.forEach((value, key) => {
370374
ret = ret.concat(value.filter(x => x.showInDocumentSymbols !== false).map((declaration: DeclarationSymbol): DocumentSymbol => mapDeclarationSymbolToDocumentSymbol(declaration, this.context)))
@@ -374,12 +378,27 @@ export class AST {
374378
}
375379

376380
public renameSymbol(pos: Position, newName: string): WorkspaceEdit | null {
377-
const occurences = this.getOccurences(pos);
378-
if (occurences.length === 0) return null;
381+
// Currently, trying to rename an imported function has two issues
382+
// The first issue is that we should be replacing the import statement with import { <old name> as <new name> } from "<module name>";
383+
// The second issue is that in a import specifier, there is an imported and local field.
384+
// Imported is an identifier node for actual name of the function that was imported
385+
// Local is also an identifier node for the alias of the function that was imported
386+
// If the function was not renamed, both imported and local will have the same location
387+
// This leads to an error in the client, as there are two changes to the program that have overlapping ranges
388+
// For now, if the name that the user is trying to rename is an imported name, we don't allow renames
389+
// I will leave this for a future CP3108 student to fix :)
390+
const identifier = this.findIdentifierNode(vsPosToEsPos(pos));
391+
if (!identifier) return null;
392+
393+
const declaration = this.findDeclarationByName(identifier.name, identifier.loc!);
394+
if (!declaration || declaration.declarationKind === DeclarationKind.KIND_IMPORT) return null;
395+
396+
const occurences = this.getOccurences(pos, identifier, declaration);
397+
if (occurences.length === 0 || occurences.some(x => x.start.line < this.prependLines)) return null;
379398

380399
return {
381400
changes: {
382-
[this.uri]: occurences.map(loc => TextEdit.replace(loc.range, newName))
401+
[this.uri]: occurences.map(loc => TextEdit.replace(loc, newName))
383402
}
384403
};
385404
}
@@ -444,33 +463,30 @@ export class AST {
444463
return autocomplete_labels[this.context.chapter - 1]
445464
.concat(ret)
446465
.concat(module_autocomplete.map((item: CompletionItem): CompletionItem => {
447-
if (this.imported_names[item.data.module_name]) {
448-
if (this.imported_names[item.data.module_name].has(item.label)) {
466+
// Logic for auto imports
467+
const map = this.imported_names[item.data.module_name];
468+
if (map) {
469+
if (map.has(item.label)) {
449470
return {
450471
...item,
451-
label: this.imported_names[item.data.module_name].get(item.label)!.local,
472+
label: map.get(item.label)!.local,
452473
detail: `Imported from ${item.data.module_name}`,
453474
data: { type: AUTOCOMPLETE_TYPES.SYMBOL, ...item.data }
454475
};
455476
}
456-
else {
457-
// Not sure if the map preserves the order that names were inserted
458-
let last_imported_range: Range = { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } };
459-
this.imported_names[item.data.module_name].forEach(range => {
460-
last_imported_range = findLastRange(last_imported_range, range.range);
461-
});
477+
else if ([...map][map.size-1][1].range.start.line >= this.prependLines){
462478
return {
463479
...item,
464480
additionalTextEdits: [
465-
TextEdit.insert(last_imported_range.end, `, ${item.label}`)
481+
TextEdit.insert([...map][map.size-1][1].range.end, `, ${item.label}`)
466482
]
467483
};
468484
};
469485
}
470-
else return {
486+
return {
471487
...item,
472488
additionalTextEdits: [
473-
TextEdit.insert({ line: 0, character: 0 }, `import { ${item.label} } from "${item.data.module_name}";\n`)
489+
TextEdit.insert({ line: this.prependLines, character: 0 }, `import { ${item.label} } from "${item.data.module_name}";\n`)
474490
]
475491
};
476492
}))

server/src/server.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {
1616
WorkspaceEdit,
1717
DocumentSymbol,
1818
HoverParams,
19-
Hover
19+
Hover,
20+
DocumentHighlight,
2021
} from 'vscode-languageserver/node';
2122

2223
import { TextDocument } from 'vscode-languageserver-textdocument';
@@ -50,6 +51,7 @@ let astCache: Map<string, AST> = new Map();
5051
function getAST(uri: string): AST {
5152
if (astCache.has(uri)) return astCache.get(uri)!;
5253

54+
// const ast = new AST(documents.get(uri)!.getText(), context, uri, 4);
5355
const ast = new AST(documents.get(uri)!.getText(), context, uri);
5456
astCache.set(uri, ast);
5557
return ast;
@@ -78,7 +80,7 @@ connection.onInitialize((params: InitializeParams) => {
7880
documentHighlightProvider: true,
7981
documentSymbolProvider: true,
8082
renameProvider: true,
81-
hoverProvider: true
83+
hoverProvider: true,
8284
}
8385
};
8486
if (hasWorkspaceFolderCapability) {
@@ -122,7 +124,7 @@ documents.onDidChangeContent(change => {
122124
timeout = setTimeout(() => {
123125
astCache.delete(change.document.uri);
124126
validateTextDocument(change.document);
125-
}, 300);
127+
}, 100);
126128
});
127129

128130
async function validateTextDocument(document: TextDocument): Promise<void> {
@@ -182,7 +184,7 @@ connection.onDocumentHighlight((params: DocumentHighlightParams) => {
182184
const document = documents.get(params.textDocument.uri);
183185
if (!document) return null;
184186

185-
return getAST(params.textDocument.uri).getOccurences(params.position);
187+
return getAST(params.textDocument.uri).getOccurences(params.position).map(x => ({ range: x}));
186188
})
187189

188190
connection.onDocumentSymbol(async (params: DocumentSymbolParams): Promise<DocumentSymbol[] | null> => {
@@ -207,7 +209,6 @@ connection.onHover((params: HoverParams): Hover | null => {
207209
return getAST(params.textDocument.uri).onHover(position);
208210
})
209211

210-
211212
// Make the text document manager listen on the connection
212213
documents.listen(connection);
213214

test_files/functions.source

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
// PREPENDED LINES, DO NOT EDIT
2+
import { black } from "rune";
3+
const foo = (x, y) => x*y;
4+
// END OF PREPEND
15
function test(arg1, ...args) {
6+
27
}
38

49
const x = () => return 0
510
x();
611

712
let y = 1;
813
y(); // Runtime error
14+
15+
foo(10, 5);

0 commit comments

Comments
 (0)