1
1
import * as vscode from 'vscode' ;
2
2
import { AutoLispExt } from '../extension' ;
3
- import { LispContainer } from '../format/sexpression' ;
3
+ import { ILispFragment } from '../format/sexpression' ;
4
4
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' ;
44
10
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 ) ) ;
53
55
}
56
+
57
+ return context . isFuncLike ? processAsFunctionReference ( context ) : processAsVariableReference ( context ) ;
54
58
}
55
59
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 ;
72
74
}
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 ) ;
78
78
}
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 ) ) ;
83
83
}
84
84
}
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 ) ] ;
96
98
}
97
99
}
98
- return result ;
100
+ // if no better occurrence found, then just regurgitate the starting location
101
+ return [ convertReferenceToLocation ( context . reference ) ] ;
99
102
}
100
103
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
+ } ;
117
129
}
118
130
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 ;
135
140
}
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
+ }
137
151
return result ;
138
152
}
139
153
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