1
1
import * as vscode from "vscode" ;
2
2
import { AtelierAPI } from "../api" ;
3
3
import { DocumentContentProvider } from "./DocumentContentProvider" ;
4
+ import { filesystemSchemas } from "../extension" ;
5
+ import { fileSpecFromURI } from "../utils/FileProviderUtil" ;
4
6
5
7
export class WorkspaceSymbolProvider implements vscode . WorkspaceSymbolProvider {
6
- private sql : string =
7
- "SELECT * FROM (" +
8
- "SELECT Name, Parent->ID AS Parent, 'method' AS Type FROM %Dictionary.MethodDefinition" +
9
- " UNION ALL %PARALLEL " +
10
- "SELECT Name, Parent->ID AS Parent, 'property' AS Type FROM %Dictionary.PropertyDefinition" +
11
- " UNION ALL %PARALLEL " +
12
- "SELECT Name, Parent->ID AS Parent, 'parameter' AS Type FROM %Dictionary.ParameterDefinition" +
13
- " UNION ALL %PARALLEL " +
14
- "SELECT Name, Parent->ID AS Parent, 'index' AS Type FROM %Dictionary.IndexDefinition" +
15
- " UNION ALL %PARALLEL " +
16
- "SELECT Name, Parent->ID AS Parent, 'foreignkey' AS Type FROM %Dictionary.ForeignKeyDefinition" +
17
- " UNION ALL %PARALLEL " +
18
- "SELECT Name, Parent->ID AS Parent, 'xdata' AS Type FROM %Dictionary.XDataDefinition" +
19
- " UNION ALL %PARALLEL " +
20
- "SELECT Name, Parent->ID AS Parent, 'query' AS Type FROM %Dictionary.QueryDefinition" +
21
- " UNION ALL %PARALLEL " +
22
- "SELECT Name, Parent->ID AS Parent, 'trigger' AS Type FROM %Dictionary.TriggerDefinition" +
23
- " UNION ALL %PARALLEL " +
24
- "SELECT Name, Parent->ID AS Parent, 'storage' AS Type FROM %Dictionary.StorageDefinition" +
25
- " UNION ALL %PARALLEL " +
26
- "SELECT Name, Parent->ID AS Parent, 'projection' AS Type FROM %Dictionary.ProjectionDefinition" +
27
- ") WHERE %SQLUPPER Name %MATCHES ?" ;
8
+ private readonly _sqlPrefix : string =
9
+ "SELECT mem.Name, mem.Parent, mem.Type FROM (" +
10
+ " SELECT Name, Name AS Parent, 'Class' AS Type FROM %Dictionary.ClassDefinition" +
11
+ " UNION SELECT Name, Parent->ID AS Parent, 'Method' AS Type FROM %Dictionary.MethodDefinition" +
12
+ " UNION SELECT Name, Parent->ID AS Parent, 'Property' AS Type FROM %Dictionary.PropertyDefinition" +
13
+ " UNION SELECT Name, Parent->ID AS Parent, 'Parameter' AS Type FROM %Dictionary.ParameterDefinition" +
14
+ " UNION SELECT Name, Parent->ID AS Parent, 'Index' AS Type FROM %Dictionary.IndexDefinition" +
15
+ " UNION SELECT Name, Parent->ID AS Parent, 'ForeignKey' AS Type FROM %Dictionary.ForeignKeyDefinition" +
16
+ " UNION SELECT Name, Parent->ID AS Parent, 'XData' AS Type FROM %Dictionary.XDataDefinition" +
17
+ " UNION SELECT Name, Parent->ID AS Parent, 'Query' AS Type FROM %Dictionary.QueryDefinition" +
18
+ " UNION SELECT Name, Parent->ID AS Parent, 'Trigger' AS Type FROM %Dictionary.TriggerDefinition" +
19
+ " UNION SELECT Name, Parent->ID AS Parent, 'Storage' AS Type FROM %Dictionary.StorageDefinition" +
20
+ " UNION SELECT Name, Parent->ID AS Parent, 'Projection' AS Type FROM %Dictionary.ProjectionDefinition" +
21
+ ") AS mem JOIN " ;
28
22
29
- private sqlNoSystem : string =
30
- "SELECT dict.Name, dict.Parent, dict.Type FROM (" +
31
- "SELECT Name, Parent->ID AS Parent, 'method' AS Type FROM %Dictionary.MethodDefinition" +
32
- " UNION ALL %PARALLEL " +
33
- "SELECT Name, Parent->ID AS Parent, 'property' AS Type FROM %Dictionary.PropertyDefinition" +
34
- " UNION ALL %PARALLEL " +
35
- "SELECT Name, Parent->ID AS Parent, 'parameter' AS Type FROM %Dictionary.ParameterDefinition" +
36
- " UNION ALL %PARALLEL " +
37
- "SELECT Name, Parent->ID AS Parent, 'index' AS Type FROM %Dictionary.IndexDefinition" +
38
- " UNION ALL %PARALLEL " +
39
- "SELECT Name, Parent->ID AS Parent, 'foreignkey' AS Type FROM %Dictionary.ForeignKeyDefinition" +
40
- " UNION ALL %PARALLEL " +
41
- "SELECT Name, Parent->ID AS Parent, 'xdata' AS Type FROM %Dictionary.XDataDefinition" +
42
- " UNION ALL %PARALLEL " +
43
- "SELECT Name, Parent->ID AS Parent, 'query' AS Type FROM %Dictionary.QueryDefinition" +
44
- " UNION ALL %PARALLEL " +
45
- "SELECT Name, Parent->ID AS Parent, 'trigger' AS Type FROM %Dictionary.TriggerDefinition" +
46
- " UNION ALL %PARALLEL " +
47
- "SELECT Name, Parent->ID AS Parent, 'storage' AS Type FROM %Dictionary.StorageDefinition" +
48
- " UNION ALL %PARALLEL " +
49
- "SELECT Name, Parent->ID AS Parent, 'projection' AS Type FROM %Dictionary.ProjectionDefinition" +
50
- ") AS dict, (" +
51
- "SELECT Name FROM %Library.RoutineMgr_StudioOpenDialog(?,?,?,?,?,?,?)" +
52
- ") AS sod WHERE %SQLUPPER dict.Name %MATCHES ? AND {fn CONCAT(dict.Parent,'.cls')} = sod.Name" ;
23
+ private readonly _sqlPrj : string =
24
+ "%Studio.Project_ProjectItemsList(?) AS pil ON mem.Parent = pil.Name AND pil.Type = 'CLS'" ;
53
25
54
- private queryResultToSymbols ( data : any , folderUri : vscode . Uri ) {
26
+ private readonly _sqlDocs : string =
27
+ "%Library.RoutineMgr_StudioOpenDialog(?,1,1,?,1,0,?,'Type = 4',0,?) AS sod ON mem.Parent = $EXTRACT(sod.Name,1,$LENGTH(sod.Name)-4)" ;
28
+
29
+ private readonly _sqlSuffix : string = " WHERE mem.Name LIKE ? ESCAPE '\\'" ;
30
+
31
+ /**
32
+ * Convert the query results to VS Code symbols. Needs to be typed as `any[]`
33
+ * because we aren't including ranges. They will be resolved later.
34
+ */
35
+ private _queryResultToSymbols ( data : any , wsFolder : vscode . WorkspaceFolder ) : any [ ] {
55
36
const result = [ ] ;
56
37
const uris : Map < string , vscode . Uri > = new Map ( ) ;
57
38
for ( const element of data . result . content ) {
58
39
const kind : vscode . SymbolKind = ( ( ) => {
59
40
switch ( element . Type ) {
60
- case "query " :
61
- case "method " :
41
+ case "Query " :
42
+ case "Method " :
62
43
return vscode . SymbolKind . Method ;
63
- case "parameter " :
44
+ case "Parameter " :
64
45
return vscode . SymbolKind . Constant ;
65
- case "index " :
46
+ case "Index " :
66
47
return vscode . SymbolKind . Key ;
67
- case "xdata " :
68
- case "storage " :
48
+ case "XData " :
49
+ case "Storage " :
69
50
return vscode . SymbolKind . Struct ;
70
- case "property" :
51
+ case "Class" :
52
+ return vscode . SymbolKind . Class ;
71
53
default :
72
54
return vscode . SymbolKind . Property ;
73
55
}
@@ -77,14 +59,21 @@ export class WorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
77
59
if ( uris . has ( element . Parent ) ) {
78
60
uri = uris . get ( element . Parent ) ;
79
61
} else {
80
- uri = DocumentContentProvider . getUri ( `${ element . Parent } .cls` , undefined , undefined , undefined , folderUri ) ;
62
+ uri = DocumentContentProvider . getUri (
63
+ `${ element . Parent } .cls` ,
64
+ wsFolder . name ,
65
+ undefined ,
66
+ undefined ,
67
+ wsFolder . uri ,
68
+ // Only "file" scheme is fully supported for client-side editing
69
+ wsFolder . uri . scheme != "file"
70
+ ) ;
81
71
uris . set ( element . Parent , uri ) ;
82
72
}
83
73
84
74
result . push ( {
85
75
name : element . Name ,
86
- containerName :
87
- element . Type === "foreignkey" ? "ForeignKey" : element . Type . charAt ( 0 ) . toUpperCase ( ) + element . Type . slice ( 1 ) ,
76
+ containerName : element . Type ,
88
77
kind,
89
78
location : {
90
79
uri,
@@ -94,96 +83,70 @@ export class WorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
94
83
return result ;
95
84
}
96
85
97
- public async provideWorkspaceSymbols ( query : string ) : Promise < vscode . SymbolInformation [ ] > {
98
- if ( query . length === 0 ) {
99
- return null ;
100
- }
101
- // Convert query to a %MATCHES compatible pattern
102
- let pattern = "" ;
103
- for ( let i = 0 ; i < query . length ; i ++ ) {
104
- const char = query . charAt ( i ) ;
105
- pattern += char === "*" || char === "?" ? `*\\${ char } ` : `*${ char } ` ;
106
- }
107
- pattern = pattern . toUpperCase ( ) + "*" ;
108
- // Filter the folders to search so we don't query the same ns on the same server twice
109
- const serversToQuery : {
110
- api : AtelierAPI ;
111
- uri : vscode . Uri ;
112
- system : boolean ;
113
- } [ ] = [ ] ;
114
- for ( const folder of vscode . workspace . workspaceFolders ) {
115
- const folderApi = new AtelierAPI ( folder . uri ) ;
116
- const found = serversToQuery . findIndex (
117
- ( server ) =>
118
- server . api . config . host . toLowerCase ( ) === folderApi . config . host . toLowerCase ( ) &&
119
- server . api . config . port === folderApi . config . port &&
120
- server . api . config . pathPrefix . toLowerCase ( ) === folderApi . config . pathPrefix . toLowerCase ( ) &&
121
- server . api . config . ns . toLowerCase ( ) === folderApi . config . ns . toLowerCase ( )
122
- ) ;
123
- if ( found === - 1 ) {
124
- serversToQuery . push ( {
125
- api : folderApi ,
126
- uri : folder . uri ,
127
- system : true ,
128
- } ) ;
129
- } else if ( serversToQuery [ found ] . uri . scheme . startsWith ( "isfs" ) && ! folder . uri . scheme . startsWith ( "isfs" ) ) {
130
- // If we have multiple folders connected to the same server and ns
131
- // and one is not isfs, keep the non-isfs one
132
- serversToQuery [ found ] . uri = folder . uri ;
133
- }
134
- }
135
- serversToQuery . map ( ( server ) => {
136
- if ( server . api . config . ns . toLowerCase ( ) !== "%sys" ) {
137
- const found = serversToQuery . findIndex (
138
- ( server2 ) =>
139
- server2 . api . config . host . toLowerCase ( ) === server . api . config . host . toLowerCase ( ) &&
140
- server2 . api . config . port === server . api . config . port &&
141
- server2 . api . config . pathPrefix . toLowerCase ( ) === server . api . config . pathPrefix . toLowerCase ( ) &&
142
- server2 . api . config . ns . toLowerCase ( ) === "%sys"
143
- ) ;
144
- if ( found !== - 1 ) {
145
- server . system = false ;
146
- }
147
- }
148
- return server ;
149
- } ) ;
86
+ public async provideWorkspaceSymbols (
87
+ query : string ,
88
+ token : vscode . CancellationToken
89
+ ) : Promise < vscode . SymbolInformation [ ] > {
90
+ if ( ! vscode . workspace . workspaceFolders ?. length ) return ;
91
+ // Convert query to a LIKE compatible pattern
92
+ let pattern = "%" ;
93
+ for ( const c of query ) pattern += `${ [ "_" , "%" , "\\" ] . includes ( c ) ? "\\" : "" } ${ c } %` ;
94
+ if ( token . isCancellationRequested ) return ;
95
+ // Get results for all workspace folders
150
96
return Promise . allSettled (
151
- serversToQuery
152
- . map ( ( server ) => {
153
- // Set the system property so we don't show system items multiple times if this
154
- // workspace is connected to both the %SYS and a non-%SYS namespace on the same server
155
- if ( server . api . config . ns . toLowerCase ( ) !== "%sys" ) {
156
- const found = serversToQuery . findIndex (
157
- ( server2 ) =>
158
- server2 . api . config . host . toLowerCase ( ) === server . api . config . host . toLowerCase ( ) &&
159
- server2 . api . config . port === server . api . config . port &&
160
- server2 . api . config . pathPrefix . toLowerCase ( ) === server . api . config . pathPrefix . toLowerCase ( ) &&
161
- server2 . api . config . ns . toLowerCase ( ) === "%sys"
162
- ) ;
163
- if ( found !== - 1 ) {
164
- server . system = false ;
165
- }
97
+ vscode . workspace . workspaceFolders . map ( ( wsFolder ) => {
98
+ if ( filesystemSchemas . includes ( wsFolder . uri . scheme ) ) {
99
+ const params = new URLSearchParams ( wsFolder . uri . query ) ;
100
+ if ( params . has ( "csp" ) && [ "" , "1" ] . includes ( params . get ( "csp" ) ) ) {
101
+ // No classes or class members in web application folders
102
+ return Promise . resolve ( [ ] ) ;
103
+ } else {
104
+ const api = new AtelierAPI ( wsFolder . uri ) ;
105
+ if ( ! api . active || token . isCancellationRequested ) return Promise . resolve ( [ ] ) ;
106
+ const project = params . get ( "project" ) ?? "" ;
107
+ return api
108
+ . actionQuery ( `${ this . _sqlPrefix } ${ project . length ? this . _sqlPrj : this . _sqlDocs } ${ this . _sqlSuffix } ` , [
109
+ project . length ? project : fileSpecFromURI ( wsFolder . uri ) ,
110
+ params . has ( "system" ) && params . get ( "system" ) . length
111
+ ? params . get ( "system" )
112
+ : api . ns == "%SYS"
113
+ ? "1"
114
+ : "0" ,
115
+ params . has ( "generated" ) && params . get ( "generated" ) . length ? params . get ( "generated" ) : "0" ,
116
+ params . has ( "mapped" ) && params . get ( "mapped" ) == "0" ? "0" : "1" ,
117
+ pattern ,
118
+ ] )
119
+ . then ( ( data ) => ( token . isCancellationRequested ? [ ] : this . _queryResultToSymbols ( data , wsFolder ) ) ) ;
166
120
}
167
- return server ;
168
- } )
169
- . map ( ( server ) =>
170
- server . system
171
- ? server . api . actionQuery ( this . sql , [ pattern ] ) . then ( ( data ) => this . queryResultToSymbols ( data , server . uri ) )
172
- : server . api
173
- . actionQuery ( this . sqlNoSystem , [ "*.cls" , "1" , "1" , "0" , "1" , "0" , "0" , pattern ] )
174
- . then ( ( data ) => this . queryResultToSymbols ( data , server . uri ) )
175
- )
176
- ) . then ( ( results ) => results . flatMap ( ( result ) => ( result . status === "fulfilled" ? result . value : [ ] ) ) ) ;
121
+ } else {
122
+ // Client-side folders should use the isfs default parameters
123
+ const api = new AtelierAPI ( wsFolder . uri ) ;
124
+ if ( ! api . active || token . isCancellationRequested ) return Promise . resolve ( [ ] ) ;
125
+ return api
126
+ . actionQuery ( ` ${ this . _sqlPrefix } ${ this . _sqlDocs } ${ this . _sqlSuffix } ` , [ "*.cls" , "0" , "0" , "1" , pattern ] )
127
+ . then ( ( data ) => ( token . isCancellationRequested ? [ ] : this . _queryResultToSymbols ( data , wsFolder ) ) ) ;
128
+ }
129
+ } )
130
+ ) . then ( ( results ) => results . flatMap ( ( result ) => ( result . status == "fulfilled" ? result . value : [ ] ) ) ) ;
177
131
}
178
132
179
133
resolveWorkspaceSymbol ( symbol : vscode . SymbolInformation ) : vscode . ProviderResult < vscode . SymbolInformation > {
180
134
return vscode . commands
181
135
. executeCommand < vscode . DocumentSymbol [ ] > ( "vscode.executeDocumentSymbolProvider" , symbol . location . uri )
182
136
. then ( ( docSymbols ) => {
183
- for ( const docSymbol of docSymbols [ 0 ] . children ) {
184
- if ( docSymbol . name === symbol . name && docSymbol . kind === symbol . kind ) {
185
- symbol . location . range = docSymbol . selectionRange ;
186
- break ;
137
+ if ( ! Array . isArray ( docSymbols ) || ! docSymbols . length ) return ;
138
+ if ( symbol . kind == vscode . SymbolKind . Class ) {
139
+ symbol . location . range = docSymbols [ 0 ] . selectionRange ;
140
+ } else {
141
+ const memberType = symbol . containerName . toUpperCase ( ) ;
142
+ const unquote = ( n : string ) : string => {
143
+ return n [ 0 ] == '"' ? n . slice ( 1 , - 1 ) . replace ( / " " / g, '"' ) : n ;
144
+ } ;
145
+ for ( const docSymbol of docSymbols [ 0 ] . children ) {
146
+ if ( unquote ( docSymbol . name ) == symbol . name && docSymbol . detail . toUpperCase ( ) . includes ( memberType ) ) {
147
+ symbol . location . range = docSymbol . selectionRange ;
148
+ break ;
149
+ }
187
150
}
188
151
}
189
152
return symbol ;
0 commit comments