From a37e4957089dc4ecbb8596db3a8b34aa1626a8cb Mon Sep 17 00:00:00 2001 From: Gayan Perera Date: Fri, 22 Jul 2022 15:44:16 +0200 Subject: [PATCH] Support viewing inherited members through QuickPick UI. - The QuickPick view API is used to show the document outline with inherited symbols using the new language server extension method, 'java/extendedDocumentSymbol'. - When ctrl+o is activated from the quick-pick menu (eg. the outline view is already active), the extended outline is then activated Co-Authored-by: Gayan Perera Co-Authored-by: Roland Grunberg --- package.json | 11 +++- package.nls.json | 3 +- src/commands.ts | 5 ++ src/outline/extendedOutlineQuickPick.ts | 81 +++++++++++++++++++++++++ src/outline/protocol.ts | 10 +++ src/standardLanguageClient.ts | 14 ++++- src/themeUtils.ts | 20 ++++++ src/typeHierarchy/model.ts | 16 +---- 8 files changed, 143 insertions(+), 17 deletions(-) create mode 100644 src/outline/extendedOutlineQuickPick.ts create mode 100644 src/outline/protocol.ts create mode 100644 src/themeUtils.ts diff --git a/package.json b/package.json index a68bac8728..d44eacdacd 100644 --- a/package.json +++ b/package.json @@ -1727,7 +1727,11 @@ }, { "command": "java.change.searchScope", - "title": "%java.change.searchScope%", + "title": "%java.change.searchScope%" + }, + { + "command": "java.action.showExtendedOutline", + "title": "%java.action.showExtendedOutline%", "category": "Java" } ], @@ -1757,6 +1761,11 @@ "command": "java.action.doCleanup", "key": "ctrl+shift+alt+s", "when": "javaLSReady && editorLangId == java" + }, + { + "command": "java.action.showExtendedOutline", + "key": "ctrl+o", + "when": "javaLSReady && inQuickOpen" } ], "menus": { diff --git a/package.nls.json b/package.nls.json index 76585572b3..33307fcaae 100644 --- a/package.nls.json +++ b/package.nls.json @@ -28,5 +28,6 @@ "java.edit.smartSemicolonDetection": "Java Smart Semicolon Detection", "java.action.filesExplorerPasteAction": "Paste Clipboard Text Into a File", "java.action.doCleanup": "Performs Cleanup Actions", - "java.change.searchScope": "Change Search Scope" + "java.change.searchScope": "Change Search Scope", + "java.action.showExtendedOutline": "Open Extended Outline" } diff --git a/src/commands.ts b/src/commands.ts index 4e9a9b123e..3466633f78 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -361,6 +361,11 @@ export namespace Commands { */ export const CHANGE_JAVA_SEARCH_SCOPE = "java.change.searchScope"; + /** + * Show Extended Outline for current document. + */ + export const SHOW_EXTEND_OUTLINE = 'java.action.showExtendedOutline'; + } /** diff --git a/src/outline/extendedOutlineQuickPick.ts b/src/outline/extendedOutlineQuickPick.ts new file mode 100644 index 0000000000..794fa25068 --- /dev/null +++ b/src/outline/extendedOutlineQuickPick.ts @@ -0,0 +1,81 @@ +import { DocumentSymbolParams, LanguageClient, TextDocumentIdentifier } from "vscode-languageclient/node"; +import { getActiveLanguageClient } from "../extension"; +import { ExtendedDocumentSymbolRequest } from "./protocol"; +import { Location, Position, QuickPick, QuickPickItem, Uri, window, workspace } from "vscode"; +import { getLThemeIcon } from "../themeUtils"; + +export class ExtendedOutlineQuickPick { + private api: QuickPick; + private client: LanguageClient; + public initialized: boolean; + + constructor() { + this.initialized = false; + } + + async initialize() { + this.api = window.createQuickPick(); + this.api.ignoreFocusOut = true; + this.api.onDidChangeActive((items: QuickPickItem[]) => { + if (items.length > 0) { + const active: QuickPickItem = items[0]; + const uri = active["uri"]; + const range = active["range"]; + if (uri !== undefined) { + workspace.openTextDocument(Uri.parse(uri)).then(doc => { + window.showTextDocument(doc, {preserveFocus: true, selection: range}); + }); + } else { + window.showTextDocument(window.activeTextEditor.document, {preserveFocus: true, selection: range}); + } + } + }); + this.api.onDidAccept(() => { + this.api.hide(); + }); + this.client = await getActiveLanguageClient(); + this.initialized = true; + } + + async open(uri: Uri) { + if (!this.initialized) { + await this.initialize(); + } + + if (!this.api) { + return; + } + + const location = new Location(uri, new Position(0, 0)); + const params: DocumentSymbolParams = { + textDocument: TextDocumentIdentifier.create(location.uri.toString()) + }; + const symbols = await this.client.sendRequest(ExtendedDocumentSymbolRequest.type, params); + let quickPickItems: QuickPickItem[] = []; + for (const s of symbols) { + const icon = getLThemeIcon(s.kind).id; + const item = { + label: `$(${icon}) ${s.name}`, + description: s.detail.trim(), + uri: s.uri, + range: s.range + }; + quickPickItems.push(item); + if (icon === 'symbol-class') { + const items: QuickPickItem[] = s.children.map(s => ({ + label: `$(${getLThemeIcon(s.kind).id}) ${s.name}`, + // custom quick pick has automatic space between label & description + description: s.detail.trim(), + uri: s.uri, + range: s.range + })); + quickPickItems = quickPickItems.concat(items); + } + } + this.api.items = quickPickItems; + this.api.activeItems = []; + this.api.show(); + } +} + +export const extendedOutlineQuickPick: ExtendedOutlineQuickPick = new ExtendedOutlineQuickPick(); diff --git a/src/outline/protocol.ts b/src/outline/protocol.ts new file mode 100644 index 0000000000..ffba1508a7 --- /dev/null +++ b/src/outline/protocol.ts @@ -0,0 +1,10 @@ +import { DocumentSymbol, DocumentSymbolParams, RequestType } from "vscode-languageclient"; + +export namespace ExtendedDocumentSymbolRequest { + export const type = new RequestType('java/extendedDocumentSymbol'); +} + +export interface ExtendedDocumentSymbol extends DocumentSymbol { + uri: string; + children?: ExtendedDocumentSymbol[]; +} \ No newline at end of file diff --git a/src/standardLanguageClient.ts b/src/standardLanguageClient.ts index 5d65e9f59d..2eaed4fbdc 100644 --- a/src/standardLanguageClient.ts +++ b/src/standardLanguageClient.ts @@ -41,6 +41,7 @@ import { registerDocumentValidationListener } from './diagnostic'; import { listJdks, sortJdksBySource, sortJdksByVersion } from './jdkUtils'; import { ClientCodeActionProvider } from './clientCodeActionProvider'; import { BuildFileSelector } from './buildFilesSelector'; +import { extendedOutlineQuickPick } from "./outline/extendedOutlineQuickPick"; const extensionName = 'Language Support for Java'; const GRADLE_CHECKSUM = "gradle/checksum/prompt"; @@ -593,6 +594,17 @@ export class StandardLanguageClient { } })); + context.subscriptions.push(commands.registerCommand(Commands.SHOW_EXTEND_OUTLINE, (location: any) => { + if (location instanceof Uri) { + extendedOutlineQuickPick.open(location); + } else { + if (window.activeTextEditor?.document?.languageId !== "java") { + return; + } + extendedOutlineQuickPick.open(window.activeTextEditor.document.uri); + } + })); + buildPath.registerCommands(context); sourceAction.registerCommands(this.languageClient, context); refactorAction.registerCommands(this.languageClient, context); @@ -897,4 +909,4 @@ export class DisableWillRenameFeature implements StaticFeature { fillInitializeParams?: () => void; preInitialize?: () => void; initialize(): void {} -} \ No newline at end of file +} diff --git a/src/themeUtils.ts b/src/themeUtils.ts new file mode 100644 index 0000000000..a4634e7eb8 --- /dev/null +++ b/src/themeUtils.ts @@ -0,0 +1,20 @@ +import { SymbolKind as VSymbolKind, ThemeIcon } from "vscode"; +import { SymbolKind as LSymbolKind} from "vscode-languageclient"; + +const themeIconIds = [ + 'symbol-file', 'symbol-module', 'symbol-namespace', 'symbol-package', 'symbol-class', 'symbol-method', + 'symbol-property', 'symbol-field', 'symbol-constructor', 'symbol-enum', 'symbol-interface', + 'symbol-function', 'symbol-variable', 'symbol-constant', 'symbol-string', 'symbol-number', 'symbol-boolean', + 'symbol-array', 'symbol-object', 'symbol-key', 'symbol-null', 'symbol-enum-member', 'symbol-struct', + 'symbol-event', 'symbol-operator', 'symbol-type-parameter' +]; + +export function getLThemeIcon(kind: LSymbolKind): ThemeIcon | undefined { + const id = themeIconIds[kind - 1]; + return id ? new ThemeIcon(id) : undefined; +} + +export function getThemeIcon(kind: VSymbolKind): ThemeIcon | undefined { + const id = themeIconIds[kind]; + return id ? new ThemeIcon(id) : undefined; +} \ No newline at end of file diff --git a/src/typeHierarchy/model.ts b/src/typeHierarchy/model.ts index 5161de54d9..089fc130e4 100644 --- a/src/typeHierarchy/model.ts +++ b/src/typeHierarchy/model.ts @@ -5,6 +5,7 @@ import { getActiveLanguageClient } from "../extension"; import { LanguageClient } from "vscode-languageclient/node"; import { getRootItem, resolveTypeHierarchy, typeHierarchyDirectionToContextString } from "./util"; import { CancellationToken, commands, workspace } from "vscode"; +import { getThemeIcon } from "../themeUtils"; export class TypeHierarchyTreeInput implements SymbolTreeInput { readonly contextValue: string = "javaTypeHierarchy"; @@ -124,7 +125,7 @@ class TypeHierarchyTreeDataProvider implements vscode.TreeDataProvider