|
1 | 1 | import * as vscode from "vscode";
|
2 | 2 | import { AtelierAPI } from "../api";
|
3 |
| -import { ClassDefinition } from "../utils/classDefinition"; |
| 3 | +import { currentWorkspaceFolder } from "../utils"; |
4 | 4 | import { DocumentContentProvider } from "./DocumentContentProvider";
|
5 |
| -import { config } from "../extension"; |
6 | 5 |
|
7 | 6 | export class WorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
|
8 |
| - public provideWorkspaceSymbols( |
9 |
| - query: string, |
10 |
| - token: vscode.CancellationToken |
11 |
| - ): vscode.ProviderResult<vscode.SymbolInformation[]> { |
12 |
| - if (query.length < 3) { |
| 7 | + private sql: string = |
| 8 | + "SELECT * FROM (" + |
| 9 | + "SELECT Name, Parent->ID AS Parent, 'method' AS Type FROM %Dictionary.MethodDefinition" + |
| 10 | + " UNION ALL %PARALLEL " + |
| 11 | + "SELECT Name, Parent->ID AS Parent, 'property' AS Type FROM %Dictionary.PropertyDefinition" + |
| 12 | + " UNION ALL %PARALLEL " + |
| 13 | + "SELECT Name, Parent->ID AS Parent, 'parameter' AS Type FROM %Dictionary.ParameterDefinition" + |
| 14 | + " UNION ALL %PARALLEL " + |
| 15 | + "SELECT Name, Parent->ID AS Parent, 'index' AS Type FROM %Dictionary.IndexDefinition" + |
| 16 | + " UNION ALL %PARALLEL " + |
| 17 | + "SELECT Name, Parent->ID AS Parent, 'foreignkey' AS Type FROM %Dictionary.ForeignKeyDefinition" + |
| 18 | + " UNION ALL %PARALLEL " + |
| 19 | + "SELECT Name, Parent->ID AS Parent, 'xdata' AS Type FROM %Dictionary.XDataDefinition" + |
| 20 | + " UNION ALL %PARALLEL " + |
| 21 | + "SELECT Name, Parent->ID AS Parent, 'query' AS Type FROM %Dictionary.QueryDefinition" + |
| 22 | + " UNION ALL %PARALLEL " + |
| 23 | + "SELECT Name, Parent->ID AS Parent, 'trigger' AS Type FROM %Dictionary.TriggerDefinition" + |
| 24 | + " UNION ALL %PARALLEL " + |
| 25 | + "SELECT Name, Parent->ID AS Parent, 'storage' AS Type FROM %Dictionary.StorageDefinition" + |
| 26 | + " UNION ALL %PARALLEL " + |
| 27 | + "SELECT Name, Parent->ID AS Parent, 'projection' AS Type FROM %Dictionary.ProjectionDefinition" + |
| 28 | + ") WHERE %SQLUPPER Name %MATCHES ?"; |
| 29 | + |
| 30 | + public provideWorkspaceSymbols(query: string): vscode.ProviderResult<vscode.SymbolInformation[]> { |
| 31 | + if (query.length === 0) { |
13 | 32 | return null;
|
14 | 33 | }
|
15 |
| - return Promise.all([this.byStudioDocuments(query), this.byMethods(query)]).then(([documents, methods]) => [ |
16 |
| - ...documents, |
17 |
| - ...methods, |
18 |
| - ]); |
19 |
| - } |
20 |
| - |
21 |
| - private getApi(): AtelierAPI { |
22 |
| - const currentFileUri = vscode.window.activeTextEditor?.document.uri; |
23 |
| - const firstFolder = vscode.workspace.workspaceFolders?.length ? vscode.workspace.workspaceFolders[0] : undefined; |
24 |
| - return new AtelierAPI(currentFileUri || firstFolder?.uri || ""); |
25 |
| - } |
26 |
| - |
27 |
| - private async byStudioDocuments(query: string): Promise<vscode.SymbolInformation[]> { |
28 |
| - const searchAllDocTypes = config("searchAllDocTypes"); |
29 |
| - if (searchAllDocTypes) { |
30 |
| - // Note: This query could be expensive if there are too many files available across the namespaces |
31 |
| - // configured in the current vs code workspace. However, delimiting by specific file types |
32 |
| - // means custom Studio documents cannot be found. So this is a trade off |
33 |
| - query = `*${query}*`; |
34 |
| - } else { |
35 |
| - // Default is to only search classes, routines and include files |
36 |
| - query = `*${query}*.cls,*${query}*.mac,*${query}*.int,*${query}*.inc`; |
| 34 | + let pattern = ""; |
| 35 | + for (let i = 0; i < query.length; i++) { |
| 36 | + const char = query.charAt(i); |
| 37 | + pattern += char === "*" || char === "?" ? `*\\${char}` : `*${char}`; |
37 | 38 | }
|
38 |
| - const sql = `SELECT TOP 10 Name FROM %Library.RoutineMgr_StudioOpenDialog(?,?,?,?,?,?,?)`; |
39 |
| - const api = this.getApi(); |
40 |
| - const direction = "1"; |
41 |
| - const orderBy = "1"; |
42 |
| - const systemFiles = "1"; |
43 |
| - const flat = "1"; |
44 |
| - const notStudio = "0"; |
45 |
| - const generated = "0"; |
| 39 | + const workspace = currentWorkspaceFolder(); |
| 40 | + const api = new AtelierAPI(workspace); |
| 41 | + return api.actionQuery(this.sql, [pattern.toUpperCase() + "*"]).then((data) => { |
| 42 | + const result = []; |
| 43 | + const uris: Map<string, vscode.Uri> = new Map(); |
| 44 | + for (const element of data.result.content) { |
| 45 | + const kind: vscode.SymbolKind = (() => { |
| 46 | + switch (element.Type) { |
| 47 | + case "query": |
| 48 | + case "method": |
| 49 | + return vscode.SymbolKind.Method; |
| 50 | + case "parameter": |
| 51 | + return vscode.SymbolKind.Constant; |
| 52 | + case "index": |
| 53 | + return vscode.SymbolKind.Key; |
| 54 | + case "xdata": |
| 55 | + case "storage": |
| 56 | + return vscode.SymbolKind.Struct; |
| 57 | + case "property": |
| 58 | + default: |
| 59 | + return vscode.SymbolKind.Property; |
| 60 | + } |
| 61 | + })(); |
| 62 | + |
| 63 | + let uri: vscode.Uri; |
| 64 | + if (uris.has(element.Parent)) { |
| 65 | + uri = uris.get(element.Parent); |
| 66 | + } else { |
| 67 | + uri = DocumentContentProvider.getUri(`${element.Parent}.cls`, workspace); |
| 68 | + uris.set(element.Parent, uri); |
| 69 | + } |
46 | 70 |
|
47 |
| - const kindFromName = (name: string) => { |
48 |
| - const nameLowerCase = name.toLowerCase(); |
49 |
| - return nameLowerCase.endsWith("cls") |
50 |
| - ? vscode.SymbolKind.Class |
51 |
| - : nameLowerCase.endsWith("zpm") |
52 |
| - ? vscode.SymbolKind.Module |
53 |
| - : vscode.SymbolKind.File; |
54 |
| - }; |
55 |
| - const data = await api.actionQuery(sql, [query, direction, orderBy, systemFiles, flat, notStudio, generated]); |
56 |
| - return data.result.content.map(({ Name }) => ({ |
57 |
| - kind: kindFromName(Name), |
58 |
| - location: { |
59 |
| - uri: DocumentContentProvider.getUri(Name, undefined, api.ns), |
60 |
| - }, |
61 |
| - name: Name, |
62 |
| - })); |
| 71 | + result.push({ |
| 72 | + name: element.Name, |
| 73 | + containerName: |
| 74 | + element.Type === "foreignkey" ? "ForeignKey" : element.Type.charAt(0).toUpperCase() + element.Type.slice(1), |
| 75 | + kind, |
| 76 | + location: { |
| 77 | + uri, |
| 78 | + }, |
| 79 | + }); |
| 80 | + } |
| 81 | + return result; |
| 82 | + }); |
63 | 83 | }
|
64 | 84 |
|
65 |
| - private async byMethods(query: string): Promise<vscode.SymbolInformation[]> { |
66 |
| - const api = this.getApi(); |
67 |
| - query = query.toUpperCase(); |
68 |
| - query = `*${query}*`; |
69 |
| - const getLocation = async (className, name) => { |
70 |
| - const classDef = new ClassDefinition(className, undefined, api.ns); |
71 |
| - return classDef.getMemberLocation(name); |
72 |
| - }; |
73 |
| - const sql = ` |
74 |
| - SELECT TOP 10 Parent ClassName, Name FROM %Dictionary.MethodDefinition WHERE %SQLUPPER Name %MATCHES ?`; |
75 |
| - return api |
76 |
| - .actionQuery(sql, [query]) |
77 |
| - .then((data): Promise<vscode.SymbolInformation>[] => |
78 |
| - data.result.content.map( |
79 |
| - async ({ ClassName, Name }): Promise<vscode.SymbolInformation> => |
80 |
| - new vscode.SymbolInformation(Name, vscode.SymbolKind.Method, ClassName, await getLocation(ClassName, Name)) |
81 |
| - ) |
82 |
| - ) |
83 |
| - .then((data) => Promise.all(data)); |
| 85 | + resolveWorkspaceSymbol(symbol: vscode.SymbolInformation): vscode.ProviderResult<vscode.SymbolInformation> { |
| 86 | + return vscode.commands |
| 87 | + .executeCommand<vscode.DocumentSymbol[]>("vscode.executeDocumentSymbolProvider", symbol.location.uri) |
| 88 | + .then((docSymbols) => { |
| 89 | + for (const docSymbol of docSymbols[0].children) { |
| 90 | + if (docSymbol.name === symbol.name && docSymbol.kind === symbol.kind) { |
| 91 | + symbol.location.range = docSymbol.selectionRange; |
| 92 | + break; |
| 93 | + } |
| 94 | + } |
| 95 | + return symbol; |
| 96 | + }); |
84 | 97 | }
|
85 | 98 | }
|
0 commit comments