11import * as vscode from "vscode" ;
22import { AtelierAPI } from "../api" ;
33import { DocumentContentProvider } from "./DocumentContentProvider" ;
4+ import { filesystemSchemas } from "../extension" ;
5+ import { fileSpecFromURI } from "../utils/FileProviderUtil" ;
46
57export 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 " ;
2822
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'" ;
5325
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 [ ] {
5536 const result = [ ] ;
5637 const uris : Map < string , vscode . Uri > = new Map ( ) ;
5738 for ( const element of data . result . content ) {
5839 const kind : vscode . SymbolKind = ( ( ) => {
5940 switch ( element . Type ) {
60- case "query " :
61- case "method " :
41+ case "Query " :
42+ case "Method " :
6243 return vscode . SymbolKind . Method ;
63- case "parameter " :
44+ case "Parameter " :
6445 return vscode . SymbolKind . Constant ;
65- case "index " :
46+ case "Index " :
6647 return vscode . SymbolKind . Key ;
67- case "xdata " :
68- case "storage " :
48+ case "XData " :
49+ case "Storage " :
6950 return vscode . SymbolKind . Struct ;
70- case "property" :
51+ case "Class" :
52+ return vscode . SymbolKind . Class ;
7153 default :
7254 return vscode . SymbolKind . Property ;
7355 }
@@ -77,14 +59,21 @@ export class WorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
7759 if ( uris . has ( element . Parent ) ) {
7860 uri = uris . get ( element . Parent ) ;
7961 } 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+ ) ;
8171 uris . set ( element . Parent , uri ) ;
8272 }
8373
8474 result . push ( {
8575 name : element . Name ,
86- containerName :
87- element . Type === "foreignkey" ? "ForeignKey" : element . Type . charAt ( 0 ) . toUpperCase ( ) + element . Type . slice ( 1 ) ,
76+ containerName : element . Type ,
8877 kind,
8978 location : {
9079 uri,
@@ -94,96 +83,70 @@ export class WorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
9483 return result ;
9584 }
9685
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
15096 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 ) ) ) ;
166120 }
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 : [ ] ) ) ) ;
177131 }
178132
179133 resolveWorkspaceSymbol ( symbol : vscode . SymbolInformation ) : vscode . ProviderResult < vscode . SymbolInformation > {
180134 return vscode . commands
181135 . executeCommand < vscode . DocumentSymbol [ ] > ( "vscode.executeDocumentSymbolProvider" , symbol . location . uri )
182136 . 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+ }
187150 }
188151 }
189152 return symbol ;
0 commit comments