Skip to content

Commit

Permalink
Support viewing inherited members through QuickPick UI.
Browse files Browse the repository at this point in the history
- 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 <[email protected]>
Co-Authored-by: Roland Grunberg <[email protected]>
  • Loading branch information
gayanper and rgrunber committed Feb 12, 2025
1 parent 03c4042 commit 907578a
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 17 deletions.
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
],
Expand Down Expand Up @@ -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": {
Expand Down
3 changes: 2 additions & 1 deletion package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
5 changes: 5 additions & 0 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

}

/**
Expand Down
81 changes: 81 additions & 0 deletions src/outline/extendedOutlineQuickPick.ts
Original file line number Diff line number Diff line change
@@ -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<QuickPickItem>;
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();
10 changes: 10 additions & 0 deletions src/outline/protocol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { DocumentSymbol, DocumentSymbolParams, RequestType } from "vscode-languageclient";

export namespace ExtendedDocumentSymbolRequest {
export const type = new RequestType<DocumentSymbolParams, ExtendedDocumentSymbol[], void>('java/extendedDocumentSymbol');
}

export interface ExtendedDocumentSymbol extends DocumentSymbol {
uri: string;
children?: ExtendedDocumentSymbol[];
}
14 changes: 13 additions & 1 deletion src/standardLanguageClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -897,4 +909,4 @@ export class DisableWillRenameFeature implements StaticFeature {
fillInitializeParams?: () => void;
preInitialize?: () => void;
initialize(): void {}
}
}
20 changes: 20 additions & 0 deletions src/themeUtils.ts
Original file line number Diff line number Diff line change
@@ -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;
}
16 changes: 2 additions & 14 deletions src/typeHierarchy/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TypeHierarchyItem> {
readonly contextValue: string = "javaTypeHierarchy";
Expand Down Expand Up @@ -124,7 +125,7 @@ class TypeHierarchyTreeDataProvider implements vscode.TreeDataProvider<TypeHiera
const treeItem: vscode.TreeItem = (element === this.model.getBaseItem()) ? new vscode.TreeItem({ label: element.name, highlights: [[0, element.name.length]] }) : new vscode.TreeItem(element.name);
treeItem.contextValue = (element === this.model.getBaseItem() || !element.uri) ? "false" : "true";
treeItem.description = element.detail;
treeItem.iconPath = TypeHierarchyTreeDataProvider.getThemeIcon(element.kind);
treeItem.iconPath = getThemeIcon(element.kind);
treeItem.command = (element.uri) ? {
command: 'vscode.open',
title: 'Open Type Definition Location',
Expand Down Expand Up @@ -248,17 +249,4 @@ class TypeHierarchyTreeDataProvider implements vscode.TreeDataProvider<TypeHiera
expand: false,
};
}

private static 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'
];

private static getThemeIcon(kind: vscode.SymbolKind): vscode.ThemeIcon | undefined {
const id = TypeHierarchyTreeDataProvider.themeIconIds[kind];
return id ? new vscode.ThemeIcon(id) : undefined;
}
}

0 comments on commit 907578a

Please sign in to comment.