|
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