Skip to content

Commit ca9d605

Browse files
JD-HowardJoshSen-real
authored
Provider Enhancements (#167)
* Provider Enhancements * Updated E2E version reference * Added toString to fix a vscode data type change * Hopefully resolved all associated CodeQL flags Co-authored-by: Josh <josh.howard@ifnullthen.com> Co-authored-by: linse-adsk <57521938+Sen-real@users.noreply.github.com>
1 parent a6227ed commit ca9d605

20 files changed

+598
-310
lines changed

extension/src/commands.ts

+37-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { AutoLispExt } from './extension';
44
import { openWebHelp } from './help/openWebHelp';
55
import { generateDocumentationSnippet, getDefunArguments, getDefunAtPosition } from './help/userDocumentation';
66
import { showErrorMessage } from './project/projectCommands';
7-
import { AutolispDefinitionProvider } from './providers/gotoProvider';
7+
import { AutoLispExtProvideDefinition } from './providers/gotoProvider';
8+
import { AutoLispExtProvideReferences } from './providers/referenceProvider';
89
import { AutoLispExtPrepareRename, AutoLispExtProvideRenameEdits } from './providers/renameProvider';
910
import { SymbolManager } from './symbols';
1011

@@ -72,7 +73,22 @@ export function registerCommands(context: vscode.ExtensionContext){
7273
}
7374
}));
7475

75-
AutoLispExt.Subscriptions.push(vscode.languages.registerDefinitionProvider([ 'autolisp', 'lisp'], new AutolispDefinitionProvider()));
76+
AutoLispExt.Subscriptions.push(vscode.languages.registerDefinitionProvider([ 'autolisp', 'lisp'], {
77+
provideDefinition: function (document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken)
78+
: vscode.ProviderResult<vscode.Definition | vscode.LocationLink[]> {
79+
// Purpose: locate potential source definitions of the underlying symbol
80+
try {
81+
// offload all meaningful work to something that can be tested.
82+
const result = AutoLispExtProvideDefinition(document, position);
83+
if (!result) {
84+
return;
85+
}
86+
return result;
87+
} catch (err) {
88+
return; // I don't believe this requires a localized error since VSCode has a default "no definition found" response
89+
}
90+
}
91+
}));
7692

7793
const msgRenameFail = localize("autolispext.providers.rename.failed", "The symbol was invalid for renaming operations");
7894
AutoLispExt.Subscriptions.push(vscode.languages.registerRenameProvider(['autolisp', 'lisp'], {
@@ -117,4 +133,23 @@ export function registerCommands(context: vscode.ExtensionContext){
117133
}));
118134

119135

136+
AutoLispExt.Subscriptions.push(vscode.languages.registerReferenceProvider([ 'autolisp', 'lisp'], {
137+
provideReferences: function (document: vscode.TextDocument, position: vscode.Position, context: vscode.ReferenceContext, token: vscode.CancellationToken)
138+
: vscode.ProviderResult<vscode.Location[]>
139+
{
140+
// Purpose in theory: locate scoped reference across the workspace, project and/or randomly opened documents
141+
// Purpose in practice: similar to theory, but mostly provides visibility to what our "renameProvider" would effect
142+
try {
143+
// offload all meaningful work to something that can be tested.
144+
const result = AutoLispExtProvideReferences(document, position);
145+
if (!result) {
146+
return;
147+
}
148+
return result;
149+
} catch (err) {
150+
return; // No localized error since VSCode has a default "no results" response
151+
}
152+
}
153+
}));
154+
120155
}

extension/src/completion/autocompletionProvider.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export function getLispAndDclCompletions(document: vscode.TextDocument, word: st
130130
else {
131131
return allSuggestions.filter(function(suggestion) {
132132
for (var prefix of winOnlyListFuncPrefix) {
133-
if (suggestion.label.startsWith(prefix)) {
133+
if (suggestion.label.toString().startsWith(prefix)) {
134134
return false;
135135
}
136136
}

extension/src/parsing/containers.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ export class ContainerBuildContext implements vscode.Disposable {
119119
markGloballyTaggedAtoms(): void {
120120
[...this.basicSymbolMap.keys()].forEach(key => {
121121
this.basicSymbolMap.get(key).forEach(flatIndex => {
122-
const flag1 = FlatContainerServices.verifyAtomIsDefunAndGlobalized(this.flatView, this.flatView[flatIndex])
122+
const flag = FlatContainerServices.verifyAtomIsDefunAndGlobalized(this.flatView, this.flatView[flatIndex])
123123
|| FlatContainerServices.verifyAtomIsSetqAndGlobalized(this.flatView, this.flatView[flatIndex]);
124-
if (flag1) {
124+
if (flag) {
125125
this.flatView[flatIndex].hasGlobalFlag = true;
126126
}
127127
});
+138-119
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,159 @@
11
import * as vscode from 'vscode';
22
import { AutoLispExt } from '../extension';
3-
import { LispContainer } from '../format/sexpression';
3+
import { ILispFragment } from '../format/sexpression';
44
import { ReadonlyDocument } from '../project/readOnlyDocument';
5-
import { escapeRegExp } from "../utils";
6-
import { SearchPatterns, SearchHandlers } from './providerShared';
7-
8-
export class AutolispDefinitionProvider implements vscode.DefinitionProvider{
9-
async provideDefinition(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.Location | vscode.Location[]> {
10-
const rDoc = AutoLispExt.Documents.getDocument(document);
11-
let selected = '';
12-
rDoc.atomsForest.forEach(sexp => {
13-
if (sexp instanceof LispContainer && sexp.contains(position)){
14-
const atom = sexp.getAtomFromPos(position);
15-
if (atom && !atom.isComment()) {
16-
selected = atom.symbol.replace(/^[']*/, '');
17-
}
18-
}
19-
});
20-
if (selected === '' || ['(', ')', '\'', '.', ';'].includes(selected)){
21-
return;
22-
}
23-
try {
24-
// determine scope
25-
const {isFunction, parentContainer} = SearchHandlers.getSelectionScopeOfWork(rDoc, position, selected);
26-
const locations: Array<vscode.Location> = [];
27-
if (isFunction) {
28-
// This has a "preference" for opened and project documents for actual definitions, but will only handle variables on the opened document.
29-
let possible = this.findDefunMatches(selected, [AutoLispExt.Documents.ActiveDocument]);
30-
if (possible.length === 0) {
31-
possible = this.findDefunMatches(selected, AutoLispExt.Documents.OpenedDocuments.concat(AutoLispExt.Documents.ProjectDocuments));
32-
}
33-
if (possible.length === 0) {
34-
possible = this.findDefunMatches(selected, AutoLispExt.Documents.WorkspaceDocuments);
35-
}
36-
possible.forEach(item => {
37-
locations.push(item);
38-
});
39-
} else if (parentContainer) { // Most likely a variable, but ultimately the scope of work is now only in the active document.
40-
this.findFirstVariableMatch(rDoc, position, selected, parentContainer).forEach(item => {
41-
locations.push(item);
42-
});
43-
}
5+
import { DocumentServices } from '../services/documentServices';
6+
import { FlatContainerServices } from '../services/flatContainerServices';
7+
import { SymbolServices } from '../services/symbolServices';
8+
import { ISymbolHost, ISymbolReference, IRootSymbolHost, SymbolManager } from '../symbols';
9+
import { SharedAtomic } from './providerShared';
4410

45-
if (locations.length >= 1) {
46-
const filterList = AutoLispExt.Documents.ExcludedFiles;
47-
return locations.filter(f => !filterList.includes(f.uri.fsPath));
48-
} else {
49-
return locations;
50-
}
51-
} catch (error) {
52-
return; // I don't believe this requires a localized error since VSCode has a default "no definition found" response
11+
12+
export function AutoLispExtProvideDefinition(document: vscode.TextDocument|ReadonlyDocument, position: vscode.Position) : vscode.Location[] {
13+
const roDoc = document instanceof ReadonlyDocument ? document : AutoLispExt.Documents.getDocument(document);
14+
let selectedAtom = SharedAtomic.getNonPrimitiveAtomFromPosition(roDoc, position);
15+
if (!selectedAtom || SymbolServices.isNative(selectedAtom.symbol.toLowerCase())){
16+
return null;
17+
}
18+
const result = GotoProviderSupport.getDefinitionLocations(roDoc, selectedAtom);
19+
if (result.length === 1 && result[0].range.contains(position)) {
20+
return null;
21+
} else {
22+
return result;
23+
}
24+
}
25+
26+
27+
28+
// Namespace is intentionally not exported. Nothing in here is expected to be used beyond this file.
29+
namespace GotoProviderSupport {
30+
31+
interface IDocumentAtomContext {
32+
atom: ILispFragment;
33+
symbolKey: string;
34+
symbolRefs: Array<ISymbolReference>;
35+
flatView: Array<ILispFragment>;
36+
reference: ISymbolReference;
37+
symbolMap: IRootSymbolHost;
38+
parent: ISymbolHost;
39+
isFuncLike: boolean;
40+
}
41+
42+
export function getDefinitionLocations (roDoc: ReadonlyDocument, atom: ILispFragment) : Array<vscode.Location> {
43+
const context = getAtomDocumentContext(roDoc, atom);
44+
45+
if (!context.parent.equal(context.symbolMap)) {
46+
// A localized symbol cannot have external scope, go directly to 1st parent (localization) reference
47+
return [convertReferenceToLocation(context.parent.collectAllSymbols().get(context.symbolKey)[0])];
48+
}
49+
50+
const scope = getAllMatchingVerifiedGlobalReferences(context.symbolKey);
51+
if (scope.length > 0) {
52+
// return globalized reference, we don't care if its in an opened, project or workspace context
53+
// not obvious, but this path doesn't care if its a variable or function; exported ids just win...
54+
return scope.map(iRef => convertReferenceToLocation(iRef));
5355
}
56+
57+
return context.isFuncLike ? processAsFunctionReference(context) : processAsVariableReference(context);
5458
}
5559

56-
57-
private findFirstVariableMatch(doc: ReadonlyDocument, start: vscode.Position, searchFor: string, searchIn: LispContainer): vscode.Location[] {
58-
const result: vscode.Location[] = [];
59-
const ucName = searchFor.toUpperCase();
60-
let context = searchIn.getExpressionFromPos(start);
61-
let flag = true;
62-
do {
63-
const parent = !context ? searchIn : searchIn.getParentOfExpression(context);
64-
const atom = parent?.getNthKeyAtom(0);
65-
if (atom && SearchPatterns.LOCALIZES.test(atom.symbol)) {
66-
let headers = parent.getNthKeyAtom(1);
67-
if (headers?.symbol.toUpperCase() === ucName){ // adds defun names to possible result. Especially useful for quoted function names.
68-
result.push(new vscode.Location(vscode.Uri.file(doc.fileName), new vscode.Position(headers.line, headers.column)));
69-
}
70-
if (!(headers instanceof LispContainer)){
71-
headers = parent.getNthKeyAtom(2);
60+
export function processAsFunctionReference(context: IDocumentAtomContext) : Array<vscode.Location> {
61+
// this previously prioritized different categories, but now just finds everything in the opened, project & workspace
62+
// also note that there is no special handling for already being on a function DEFUN, it always finds all variants
63+
const results: Array<vscode.Location> = [];
64+
DocumentServices.findAllDocumentsWithCustomSymbolKey(context.symbolKey).forEach(roDoc => {
65+
// hunting for non-globalized defuns, go ahead and build their IdocumentAtomContext
66+
// IF you can't get the IsDefun from the document container symbol information.... <- Investigate
67+
const flatView = roDoc.documentContainer.flatten();
68+
const possible = roDoc.documentContainer.userSymbols.get(context.symbolKey);
69+
let subContext: IDocumentAtomContext = null;
70+
for (let i = 0; i < possible.length; i++) {
71+
const possibleIndex = possible[i];
72+
if (!FlatContainerServices.getParentAtomIfDefun(flatView, possibleIndex)) {
73+
continue;
7274
}
73-
if (headers instanceof LispContainer){
74-
const found = headers.atoms.find(p => p.symbol.toUpperCase() === ucName);
75-
if (found) {
76-
result.push(new vscode.Location(vscode.Uri.file(doc.fileName), new vscode.Position(found.line, found.column)));
77-
}
75+
if (!subContext) {
76+
// this has some performance impacts so we only want to pull it once per document
77+
subContext = getAtomDocumentContext(roDoc, flatView[possibleIndex], flatView);
7878
}
79-
} else if (atom && SearchPatterns.ITERATES.test(atom.symbol)) {
80-
const tmpVar = parent.getNthKeyAtom(1);
81-
if (!(tmpVar instanceof LispContainer) && tmpVar.symbol.toUpperCase() === ucName){
82-
result.push(new vscode.Location(vscode.Uri.file(doc.fileName), new vscode.Position(tmpVar.line, tmpVar.column)));
79+
const iRef = subContext.symbolRefs.find(x => x.flatIndex === possibleIndex);
80+
if (iRef.isDefinition && iRef.findLocalizingParent().equal(subContext.symbolMap)) {
81+
// IReference is a named Defun[-q] and does not have a localization parent
82+
results.push(convertReferenceToLocation(iRef));
8383
}
8484
}
85-
if (!parent || !context || result.length > 0 || parent.equal(context)) {
86-
flag = false;
87-
} else {
88-
context = parent;
89-
}
90-
} while (flag);
91-
// If we still haven't found anything check the 1st occurrence of setq's. This will find globals setqs and nested ones possibly inside other defuns
92-
if (result.length === 0){
93-
const possible = searchIn.findChildren(SearchPatterns.DEFINES, true).filter(p => p.contains(start));
94-
if (possible.length >= 0) {
95-
this.findInSetqs(possible.pop(), ucName, doc.fileName).forEach(x => { result.push(x); });
85+
});
86+
return results;
87+
}
88+
89+
export function processAsVariableReference(context: IDocumentAtomContext) : Array<vscode.Location> {
90+
// localized and exported globals scenarios were already handled
91+
// This just deals the active document globals by walking up setq locations
92+
const existing = context.symbolMap.collectAllSymbols().get(context.symbolKey);
93+
const activeIndex = existing.indexOf(context.reference);
94+
for (let i = activeIndex - 1; i >= 0; i--) {
95+
const iRef = existing[i];
96+
if (FlatContainerServices.getParentAtomIfValidSetq(context.flatView, context.flatView[iRef.flatIndex])) {
97+
return [convertReferenceToLocation(iRef)];
9698
}
9799
}
98-
return result;
100+
// if no better occurrence found, then just regurgitate the starting location
101+
return [convertReferenceToLocation(context.reference)];
99102
}
100103

101-
private findInSetqs(sexp: LispContainer, ucName: string, fileName: string): vscode.Location[] {
102-
const result: vscode.Location[] = [];
103-
const found = sexp.findChildren(SearchPatterns.ASSIGNS, false);
104-
found.forEach(setq => {
105-
let isVar = false;
106-
let cIndex = setq.nextKeyIndex(0, true);
107-
do {
108-
const atom = setq.atoms[cIndex];
109-
if (isVar && atom?.symbol.toUpperCase() === ucName) {
110-
result.push(new vscode.Location(vscode.Uri.file(fileName), new vscode.Position(atom.line, atom.column)));
111-
}
112-
cIndex = setq.nextKeyIndex(cIndex, true);
113-
isVar = !isVar;
114-
} while (cIndex && cIndex > -1 && result.length === 0);
115-
});
116-
return result;
104+
105+
export function convertReferenceToLocation(iRef: ISymbolReference) : vscode.Location {
106+
const filePointer = vscode.Uri.file(iRef.filePath);
107+
return new vscode.Location(filePointer, iRef.range);
108+
}
109+
110+
111+
export function getAtomDocumentContext(roDoc: ReadonlyDocument, selected: ILispFragment, linearView?: Array<ILispFragment>) : IDocumentAtomContext {
112+
const key = selected.symbol.toLowerCase();
113+
const map = SymbolManager.getSymbolMap(roDoc);
114+
const pointers = map.collectAllSymbols().get(key);
115+
const reference = pointers.find(item => item.flatIndex === selected.flatIndex);
116+
if (!linearView) {
117+
linearView = roDoc.documentContainer.flatten();
118+
}
119+
return {
120+
atom: selected,
121+
flatView: linearView,
122+
parent: reference.findLocalizingParent(),
123+
symbolKey: key,
124+
symbolMap: map,
125+
symbolRefs: pointers,
126+
reference: reference,
127+
isFuncLike: FlatContainerServices.isPossibleFunctionReference(linearView, selected)
128+
};
117129
}
118130

119-
private findDefunMatches(searchFor: string, searchIn: ReadonlyDocument[]): vscode.Location[] {
120-
const result: vscode.Location[] = [];
121-
const regx = new RegExp('\\((DEFUN|DEFUN-Q)' + escapeRegExp(searchFor) + '\\(', 'ig');
122-
searchIn.forEach((doc: ReadonlyDocument) => {
123-
if (regx.test(doc.fileContent.replace(/\s/g, ''))){
124-
doc.atomsForest.forEach(atom => {
125-
if (atom instanceof LispContainer){
126-
const defs = atom.findChildren(SearchPatterns.DEFINES, true);
127-
defs.forEach(sexp => {
128-
const ptr = sexp.getNthKeyAtom(1);
129-
if (ptr.symbol.toUpperCase() === searchFor.toUpperCase()){
130-
result.push(new vscode.Location(vscode.Uri.file(doc.fileName), new vscode.Position(ptr.line, ptr.column)));
131-
}
132-
});
133-
}
134-
});
131+
export function getAllMatchingVerifiedGlobalReferences(lowerKey: string): Array<ISymbolReference> {
132+
const documentReferences = DocumentServices.findAllDocumentsWithCustomSymbolKey(lowerKey);
133+
const result: Array<ISymbolReference> = [];
134+
for (let i = 0; i < documentReferences.length; i++) {
135+
const roDoc = documentReferences[i];
136+
const flatView = roDoc.documentContainer.flatten();
137+
const possible = DocumentServices.getUnverifiedGlobalizerList(roDoc, lowerKey, flatView);
138+
if (possible.length === 0) {
139+
continue;
135140
}
136-
});
141+
142+
const symbolMap = SymbolManager.getSymbolMap(roDoc);
143+
const targets = symbolMap.collectAllSymbols().get(lowerKey);
144+
possible.forEach(atom => {
145+
const iRef = targets.find(x => x.flatIndex === atom.flatIndex);
146+
if (iRef?.findLocalizingParent().equal(symbolMap)) {
147+
result.push(iRef);
148+
}
149+
});
150+
}
137151
return result;
138152
}
139153

140-
}
154+
}
155+
156+
/**
157+
* This exports the GotoProviderSupport Namespace specifically for testing, these resources are not meant for interoperability.
158+
*/
159+
export const TDD = GotoProviderSupport;

0 commit comments

Comments
 (0)