11import * as vscode from 'vscode' ;
22import { AutoLispExt } from '../extension' ;
3- import { LispContainer } from '../format/sexpression' ;
3+ import { ILispFragment } from '../format/sexpression' ;
44import { 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