From 819c31a5670ec8985083bd1fa8d21cdf76f8a1c3 Mon Sep 17 00:00:00 2001 From: Shi Chen Date: Thu, 24 Mar 2022 09:44:59 +0800 Subject: [PATCH] change type hierarchy implementation to LSP Signed-off-by: Shi Chen --- package.json | 52 +++--- package.nls.json | 2 - package.nls.zh-cn.json | 2 - src/commands.ts | 22 +-- src/standardLanguageClient.ts | 78 +++++--- src/typeHierarchy/model.ts | 200 +++++---------------- src/typeHierarchy/protocol.ts | 35 +--- src/typeHierarchy/references-view.d.ts | 17 ++ src/typeHierarchy/typeHierarchyTree.ts | 80 ++++----- src/typeHierarchy/util.ts | 124 ++----------- test/standard-mode-suite/extension.test.ts | 11 +- 11 files changed, 211 insertions(+), 412 deletions(-) diff --git a/package.json b/package.json index 2db79ae55..a6fe30c64 100644 --- a/package.json +++ b/package.json @@ -1199,34 +1199,27 @@ "category": "Java" }, { - "command": "java.action.showTypeHierarchy", - "title": "%java.action.showTypeHierarchy%", + "command": "java.action.showClassHierarchy", + "title": "%java.action.showClassHierarchy%", + "icon": "$(type-hierarchy)", "category": "Java" }, { - "command": "java.action.showClassHierarchy", + "command": "java.action.showClassHierarchyFromReferenceView", "title": "%java.action.showClassHierarchy%", "icon": "$(type-hierarchy)", - "enablement": "typeHierarchyDirection != both && typeHierarchySymbolKind != 10", "category": "Java" }, { "command": "java.action.showSupertypeHierarchy", "title": "%java.action.showSupertypeHierarchy%", "icon": "$(type-hierarchy-super)", - "enablement": "typeHierarchyDirection != parents", "category": "Java" }, { "command": "java.action.showSubtypeHierarchy", "title": "%java.action.showSubtypeHierarchy%", "icon": "$(type-hierarchy-sub)", - "enablement": "typeHierarchyDirection != children", - "category": "Java" - }, - { - "command": "java.action.changeBaseType", - "title": "%java.action.changeBaseType%", "category": "Java" }, { @@ -1297,7 +1290,7 @@ "group": "navigation@90" }, { - "command": "java.action.showTypeHierarchy", + "command": "java.action.showClassHierarchy", "when": "javaLSReady && editorTextFocus && editorLangId == java", "group": "0_navigation@3" } @@ -1336,12 +1329,12 @@ "when": "javaLSReady" }, { - "command": "java.action.showTypeHierarchy", - "when": "javaLSReady && editorIsOpen" + "command": "java.action.showClassHierarchy", + "when": "javaLSReady && editorIsOpen && reference-list.source != typeHierarchy" }, { - "command": "java.action.showClassHierarchy", - "when": "false" + "command": "java.action.showClassHierarchyFromReferenceView", + "when": "javaLSReady && editorIsOpen && reference-list.source == typeHierarchy" }, { "command": "java.action.showSubtypeHierarchy", @@ -1351,10 +1344,6 @@ "command": "java.action.showSupertypeHierarchy", "when": "false" }, - { - "command": "java.action.changeBaseType", - "when": "false" - }, { "command": "java.project.updateSourceAttachment.command", "when": "false" @@ -1378,9 +1367,9 @@ ], "view/title": [ { - "command": "java.action.showClassHierarchy", + "command": "java.action.showClassHierarchyFromReferenceView", "group": "navigation@-1", - "when": "view == references-view.tree && reference-list.hasResult && reference-list.source == javaTypeHierarchy && typeHierarchySymbolKind != 10" + "when": "view == references-view.tree && reference-list.hasResult && reference-list.source == typeHierarchy" }, { "command": "java.action.showSupertypeHierarchy", @@ -1395,9 +1384,24 @@ ], "view/item/context": [ { - "command": "java.action.changeBaseType", + "command": "java.action.showClassHierarchy", + "group": "1", + "when": "view == references-view.tree && viewItem == java-type-item" + }, + { + "command": "java.action.showClassHierarchyFromReferenceView", + "group": "1", + "when": "view == references-view.tree && viewItem == type-item" + }, + { + "command": "java.action.showSupertypeHierarchy", + "group": "1", + "when": "view == references-view.tree && viewItem == java-type-item" + }, + { + "command": "java.action.showSubtypeHierarchy", "group": "1", - "when": "view == references-view.tree && reference-list.hasResult && reference-list.source == javaTypeHierarchy && viewItem != 'false'" + "when": "view == references-view.tree && viewItem == java-type-item" } ] } diff --git a/package.nls.json b/package.nls.json index e82f5287f..6f1ba599e 100644 --- a/package.nls.json +++ b/package.nls.json @@ -17,11 +17,9 @@ "java.project.listSourcePaths": "List All Java Source Paths", "java.show.server.task.status": "Show Build Job Status", "java.action.navigateToSuperImplementation": "Go to Super Implementation", - "java.action.showTypeHierarchy": "Show Type Hierarchy", "java.action.showClassHierarchy": "Show Class Hierarchy", "java.action.showSupertypeHierarchy": "Show Supertype Hierarchy", "java.action.showSubtypeHierarchy": "Show Subtype Hierarchy", - "java.action.changeBaseType": "Base on this Type", "java.project.createModuleInfo.command": "Create module-info.java", "java.clean.sharedIndexes": "Clean Shared Indexes" } diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index 87eb77c5f..7d67978e4 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -17,11 +17,9 @@ "java.project.listSourcePaths": "列出所有 Java 源代码路径", "java.show.server.task.status": "显示工作状态", "java.action.navigateToSuperImplementation": "转到父类实现", - "java.action.showTypeHierarchy": "显示类型层次结构", "java.action.showClassHierarchy": "显示类的继承关系", "java.action.showSupertypeHierarchy": "显示父类层次结构", "java.action.showSubtypeHierarchy": "显示子类层次结构", - "java.action.changeBaseType": "基于此类型", "java.project.createModuleInfo.command": "创建 module-info.java", "java.clean.sharedIndexes": "清理共享的索引文件" } diff --git a/src/commands.ts b/src/commands.ts index a48eeb16f..c66ab71f2 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -223,33 +223,21 @@ export namespace Commands { */ export const NAVIGATE_TO_SUPER_IMPLEMENTATION_COMMAND = 'java.action.navigateToSuperImplementation'; /** - * Open Type Hierarchy of given Selection. - */ - export const SHOW_TYPE_HIERARCHY = 'java.action.showTypeHierarchy'; - /** - * Show SuperType Hierarchy of given Selection. + * Show Supertype Hierarchy of given type. */ export const SHOW_SUPERTYPE_HIERARCHY = 'java.action.showSupertypeHierarchy'; /** - * Show SubType Hierarchy of given Selection. + * Show Subtype Hierarchy of given type. */ export const SHOW_SUBTYPE_HIERARCHY = 'java.action.showSubtypeHierarchy'; /** - * Show Type Hierarchy of given Selection. + * Show Class Hierarchy of given type. */ export const SHOW_CLASS_HIERARCHY = 'java.action.showClassHierarchy'; /** - * Change the base type of Type Hierarchy. - */ - export const CHANGE_BASE_TYPE = 'java.action.changeBaseType'; - /** - * Open the given TypeHierarchy Item. - */ - export const OPEN_TYPE_HIERARCHY = 'java.navigate.openTypeHierarchy'; - /** - * Resolve the given TypeHierarchy Item. + * Show Class Hierarchy of given type from reference view. */ - export const RESOLVE_TYPE_HIERARCHY = 'java.navigate.resolveTypeHierarchy'; + export const SHOW_CLASS_HIERARCHY_FROM_REFERENCE_VIEW = 'java.action.showClassHierarchyFromReferenceView'; /** * Show server task status */ diff --git a/src/standardLanguageClient.ts b/src/standardLanguageClient.ts index 6be234e23..5b23679fe 100644 --- a/src/standardLanguageClient.ts +++ b/src/standardLanguageClient.ts @@ -4,8 +4,9 @@ import * as fse from 'fs-extra'; import { findRuntimes } from "jdk-utils"; import * as net from 'net'; import * as path from 'path'; -import { CancellationToken, CodeActionKind, commands, ConfigurationTarget, DocumentSelector, EventEmitter, ExtensionContext, extensions, languages, Location, ProgressLocation, TextEditor, Uri, ViewColumn, window, workspace } from "vscode"; +import { CancellationToken, CodeActionKind, commands, ConfigurationTarget, DocumentSelector, EventEmitter, ExtensionContext, extensions, languages, Location, ProgressLocation, TextEditor, TypeHierarchyItem, Uri, ViewColumn, window, workspace } from "vscode"; import { ConfigurationParams, ConfigurationRequest, LanguageClientOptions, Location as LSLocation, MessageType, Position as LSPosition, TextDocumentPositionParams, WorkspaceEdit } from "vscode-languageclient"; +import { TypeHierarchyFeature } from 'vscode-languageclient/lib/common/typeHierarchy'; import { LanguageClient, StreamInfo } from "vscode-languageclient/node"; import { apiManager } from "./apiManager"; import * as buildPath from './buildpath'; @@ -34,7 +35,7 @@ import { snippetCompletionProvider } from "./snippetCompletionProvider"; import * as sourceAction from './sourceAction'; import { askForProjects, projectConfigurationUpdate, upgradeGradle } from "./standardLanguageClientUtils"; import { TracingLanguageClient } from './TracingLanguageClient'; -import { TypeHierarchyDirection, TypeHierarchyItem } from "./typeHierarchy/protocol"; +import { CodeTypeHierarchyItem, showSubtypeHierarchyReferenceViewCommand, showSupertypeHierarchyReferenceViewCommand, showTypeHierarchyReferenceViewCommand } from "./typeHierarchy/protocol"; import { typeHierarchyTree } from "./typeHierarchy/typeHierarchyTree"; import { getAllJavaProjects, getJavaConfig, getJavaConfiguration } from "./utils"; import { Telemetry } from "./telemetry"; @@ -107,7 +108,7 @@ export class StandardLanguageClient { // Create the language client and start the client. this.languageClient = new TracingLanguageClient('java', extensionName, serverOptions, clientOptions); - + this.languageClient.registerFeature(new TypeHierarchyFeature(this.languageClient)); this.registerCommandsForStandardServer(context, jdtEventEmitter); fileEventHandler.registerFileEventHandlers(this.languageClient, context); @@ -376,31 +377,68 @@ export class StandardLanguageClient { } })); - context.subscriptions.push(commands.registerCommand(Commands.SHOW_TYPE_HIERARCHY, (location: any) => { - if (location instanceof Uri) { - typeHierarchyTree.setTypeHierarchy(new Location(location, window.activeTextEditor.selection.active), TypeHierarchyDirection.both); - } else { - if (window.activeTextEditor?.document?.languageId !== "java") { - return; + context.subscriptions.push(commands.registerCommand(Commands.SHOW_CLASS_HIERARCHY, async (anchor: any) => { + try { + if (anchor instanceof Uri) { // comes from context menu + await typeHierarchyTree.setTypeHierarchy(new Location(anchor, window.activeTextEditor.selection.active)); + } else if (anchor instanceof TypeHierarchyItem) { // comes from class hierarchy view item + await typeHierarchyTree.setTypeHierarchy(new Location(anchor.uri, anchor.range.start)); + } else { // comes from command palette + if (window.activeTextEditor?.document?.languageId !== "java") { + return; + } + await typeHierarchyTree.setTypeHierarchy(new Location(window.activeTextEditor.document.uri, window.activeTextEditor.selection.active)); + } + } catch (e) { + if (e?.message) { + // show message in the selection when call from editor context menu + if (anchor instanceof Uri) { + showNoLocationFound(e.message); + } else { + window.showErrorMessage(e.message); + } } - typeHierarchyTree.setTypeHierarchy(new Location(window.activeTextEditor.document.uri, window.activeTextEditor.selection.active), TypeHierarchyDirection.both); } })); - context.subscriptions.push(commands.registerCommand(Commands.SHOW_CLASS_HIERARCHY, () => { - typeHierarchyTree.changeDirection(TypeHierarchyDirection.both); - })); - - context.subscriptions.push(commands.registerCommand(Commands.SHOW_SUPERTYPE_HIERARCHY, () => { - typeHierarchyTree.changeDirection(TypeHierarchyDirection.parents); + context.subscriptions.push(commands.registerCommand(Commands.SHOW_CLASS_HIERARCHY_FROM_REFERENCE_VIEW, async (anchor?: any) => { + try { + if (!anchor) { // comes from reference-view's title or command palette + await typeHierarchyTree.setTypeHierarchyFromReferenceView(); + } else if (anchor.item instanceof TypeHierarchyItem) { // comes from reference-view's item + await typeHierarchyTree.setTypeHierarchy(new Location(anchor.item.uri, anchor.item.range.start)); + } + } catch (e) { + if (e?.message) { + window.showErrorMessage(e.message); + } + } })); - context.subscriptions.push(commands.registerCommand(Commands.SHOW_SUBTYPE_HIERARCHY, () => { - typeHierarchyTree.changeDirection(TypeHierarchyDirection.children); + context.subscriptions.push(commands.registerCommand(Commands.SHOW_SUPERTYPE_HIERARCHY, async (anchor?: any) => { + let location: Location; + if (!anchor) { + location = typeHierarchyTree.getAnchor(); + } else if (anchor instanceof TypeHierarchyItem) { + location = new Location(anchor.uri, anchor.range.start); + } + if (location) { + await commands.executeCommand(showTypeHierarchyReferenceViewCommand); + await commands.executeCommand(showSupertypeHierarchyReferenceViewCommand, location); + } })); - context.subscriptions.push(commands.registerCommand(Commands.CHANGE_BASE_TYPE, async (item: TypeHierarchyItem) => { - typeHierarchyTree.changeBaseItem(item); + context.subscriptions.push(commands.registerCommand(Commands.SHOW_SUBTYPE_HIERARCHY, async (anchor?: any) => { + let location: Location; + if (!anchor) { + location = typeHierarchyTree.getAnchor(); + } else if (anchor instanceof TypeHierarchyItem) { + location = new Location(anchor.uri, anchor.range.start); + } + if (location) { + await commands.executeCommand(showTypeHierarchyReferenceViewCommand); + await commands.executeCommand(showSubtypeHierarchyReferenceViewCommand, location); + } })); context.subscriptions.push(commands.registerCommand(Commands.BUILD_PROJECT, async (uris: Uri[] | Uri, isFullBuild: boolean, token: CancellationToken) => { diff --git a/src/typeHierarchy/model.ts b/src/typeHierarchy/model.ts index 5161de54d..77851ed23 100644 --- a/src/typeHierarchy/model.ts +++ b/src/typeHierarchy/model.ts @@ -1,54 +1,37 @@ import * as vscode from "vscode"; -import { TypeHierarchyDirection, TypeHierarchyItem } from "./protocol"; +import { CodeTypeHierarchyItem } from "./protocol"; import { SymbolItemNavigation, SymbolTreeInput, SymbolTreeModel } from "./references-view"; import { getActiveLanguageClient } from "../extension"; -import { LanguageClient } from "vscode-languageclient/node"; -import { getRootItem, resolveTypeHierarchy, typeHierarchyDirectionToContextString } from "./util"; -import { CancellationToken, commands, workspace } from "vscode"; +import { LanguageClient, Proposed, TypeHierarchySubtypesParams, TypeHierarchySubtypesRequest } from "vscode-languageclient/node"; +import { getRootItem, toCodeTypeHierarchyItem } from "./util"; +import { CancellationToken, commands } from "vscode"; -export class TypeHierarchyTreeInput implements SymbolTreeInput { +export class TypeHierarchyTreeInput implements SymbolTreeInput { readonly contextValue: string = "javaTypeHierarchy"; readonly title: string; - readonly baseItem: TypeHierarchyItem; + readonly baseItem: CodeTypeHierarchyItem; private client: LanguageClient; - private rootItem: TypeHierarchyItem; + private rootItem: CodeTypeHierarchyItem; - constructor(readonly location: vscode.Location, readonly direction: TypeHierarchyDirection, readonly token: CancellationToken, item: TypeHierarchyItem) { + constructor(readonly location: vscode.Location, readonly token: CancellationToken, item: CodeTypeHierarchyItem) { this.baseItem = item; const isMethodHierarchy: boolean = item.data["method"] !== undefined; let methodName: string; if (isMethodHierarchy) { methodName = item.data["method_name"]; } - switch (direction) { - case TypeHierarchyDirection.both: - this.title = isMethodHierarchy ? `Method Hierarchy for ${methodName}` : "Class Hierarchy"; - break; - case TypeHierarchyDirection.parents: - this.title = isMethodHierarchy ? `Supertype (Method) Hierarchy for ${methodName}` : "Supertype Hierarchy"; - break; - case TypeHierarchyDirection.children: - this.title = isMethodHierarchy ? `Subtype (Method) Hierarchy for ${methodName}` : "Subtype Hierarchy"; - break; - default: - return; - } - + this.title = isMethodHierarchy ? `Method Hierarchy for ${methodName}` : "Class Hierarchy"; } - async resolve(): Promise> { + async resolve(): Promise> { if (!this.client) { this.client = await getActiveLanguageClient(); } - // workaround: await a second to make sure the success of reveal operation on baseItem, see: https://github.com/microsoft/vscode/issues/114989 - await new Promise((resolve) => setTimeout(() => { - resolve(); - }, 1000)); - this.rootItem = (this.direction === TypeHierarchyDirection.both) ? await getRootItem(this.client, this.baseItem, this.token) : this.baseItem; - const model: TypeHierarchyModel = new TypeHierarchyModel(this.rootItem, this.direction, this.baseItem); + this.rootItem = await getRootItem(this.client, this.baseItem, this.token); + const model: TypeHierarchyModel = new TypeHierarchyModel(this.rootItem, this.baseItem); const provider = new TypeHierarchyTreeDataProvider(model, this.client, this.token); - const treeModel: SymbolTreeModel = { + const treeModel: SymbolTreeModel = { provider: provider, message: undefined, navigation: model, @@ -56,60 +39,52 @@ export class TypeHierarchyTreeInput implements SymbolTreeInput { +export class TypeHierarchyModel implements SymbolItemNavigation { public readonly onDidChange = new vscode.EventEmitter(); public readonly onDidChangeEvent = this.onDidChange.event; - constructor(private rootItem: TypeHierarchyItem, private direction: TypeHierarchyDirection, private baseItem: TypeHierarchyItem) { } + constructor(private rootItem: CodeTypeHierarchyItem, private baseItem: CodeTypeHierarchyItem) { } - public getBaseItem(): TypeHierarchyItem { + public getBaseItem(): CodeTypeHierarchyItem { return this.baseItem; } - public getDirection(): TypeHierarchyDirection { - return this.direction; - } - - public getRootItem(): TypeHierarchyItem { + public getRootItem(): CodeTypeHierarchyItem { return this.rootItem; } - location(item: TypeHierarchyItem) { - return new vscode.Location(vscode.Uri.parse(item.uri), item.range); + location(item: CodeTypeHierarchyItem) { + return new vscode.Location(item.uri, item.range); } - nearest(uri: vscode.Uri, _position: vscode.Position): TypeHierarchyItem | undefined { + nearest(uri: vscode.Uri, _position: vscode.Position): CodeTypeHierarchyItem | undefined { return this.baseItem; } - next(from: TypeHierarchyItem): TypeHierarchyItem { + next(from: CodeTypeHierarchyItem): CodeTypeHierarchyItem { return from; } - previous(from: TypeHierarchyItem): TypeHierarchyItem { + previous(from: CodeTypeHierarchyItem): CodeTypeHierarchyItem { return from; } } -class TypeHierarchyTreeDataProvider implements vscode.TreeDataProvider { - private readonly emitter: vscode.EventEmitter = new vscode.EventEmitter(); +class TypeHierarchyTreeDataProvider implements vscode.TreeDataProvider { + private readonly emitter = new vscode.EventEmitter(); private readonly modelListener: vscode.Disposable; - private lazyLoad: boolean; - public readonly onDidChangeTreeData: vscode.Event = this.emitter.event; + public readonly onDidChangeTreeData: vscode.Event = this.emitter.event; constructor(readonly model: TypeHierarchyModel, readonly client: LanguageClient, readonly token: CancellationToken) { - this.modelListener = model.onDidChangeEvent(e => this.emitter.fire(e instanceof TypeHierarchyItem ? e : undefined)); - this.lazyLoad = workspace.getConfiguration().get("java.typeHierarchy.lazyLoad"); + this.modelListener = model.onDidChangeEvent(e => this.emitter.fire(e instanceof CodeTypeHierarchyItem ? e : undefined)); } dispose(): void { @@ -117,136 +92,47 @@ class TypeHierarchyTreeDataProvider implements vscode.TreeDataProvider { + async getTreeItem(element: CodeTypeHierarchyItem): Promise { if (!element) { return undefined; } 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.contextValue = "java-type-item"; treeItem.description = element.detail; treeItem.iconPath = TypeHierarchyTreeDataProvider.getThemeIcon(element.kind); treeItem.command = (element.uri) ? { command: 'vscode.open', title: 'Open Type Definition Location', arguments: [ - vscode.Uri.parse(element.uri), { selection: element.selectionRange } + element.uri, { selection: element.selectionRange } ] } : undefined; // workaround: set a specific id to refresh the collapsible state for treeItems, see: https://github.com/microsoft/vscode/issues/114614#issuecomment-763428052 treeItem.id = `${element.data["element"]}${Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)}`; - if (element.expand) { + if (element.children) { treeItem.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; - } else if (this.model.getDirection() === TypeHierarchyDirection.children || this.model.getDirection() === TypeHierarchyDirection.both) { - // For an unresolved baseItem, will make it collapsed to show it early. It will be automatically expanded by model.nearest() - if (element === this.model.getBaseItem()) { - if (!element.children) { - treeItem.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; - } else if (element.children.length === 0) { - treeItem.collapsibleState = vscode.TreeItemCollapsibleState.None; - } else { - treeItem.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; - } - } else { - if (!element.children) { - if (this.lazyLoad) { - treeItem.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; - return treeItem; - } - const resolvedItem = await resolveTypeHierarchy(this.client, element, this.model.getDirection(), this.token); - if (!resolvedItem) { - return undefined; - } - element.children = resolvedItem.children; - } - treeItem.collapsibleState = (element.children.length === 0) ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed; - } - } else if (this.model.getDirection() === TypeHierarchyDirection.parents) { - if (element === this.model.getBaseItem()) { - if (!element.parents) { - treeItem.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; - } else if (element.parents.length === 0) { - treeItem.collapsibleState = vscode.TreeItemCollapsibleState.None; - } else { - treeItem.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; - } - } else { - if (!element.parents) { - if (this.lazyLoad) { - treeItem.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; - return treeItem; - } - const resolvedItem = await resolveTypeHierarchy(this.client, element, this.model.getDirection(), this.token); - if (!resolvedItem) { - return undefined; - } - element.parents = resolvedItem.parents; - } - treeItem.collapsibleState = (element.parents.length === 0) ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed; - } + } else { + treeItem.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; } return treeItem; } - async getChildren(element?: TypeHierarchyItem | undefined): Promise { + async getChildren(element?: CodeTypeHierarchyItem | undefined): Promise { if (!element) { return [this.model.getRootItem()]; } - if (this.model.getDirection() === TypeHierarchyDirection.children || this.model.getDirection() === TypeHierarchyDirection.both) { - if (!element.children) { - if (TypeHierarchyTreeDataProvider.isWhiteListType(element)) { - return [TypeHierarchyTreeDataProvider.getFakeItem(element)]; - } - const resolvedItem = await resolveTypeHierarchy(this.client, element, this.model.getDirection(), this.token); - if (!resolvedItem) { - return undefined; - } - element.children = resolvedItem.children; - if (element.children.length === 0) { - this.emitter.fire(element); - } - } + if (element.children) { return element.children; - } else if (this.model.getDirection() === TypeHierarchyDirection.parents) { - if (!element.parents) { - const resolvedItem = await resolveTypeHierarchy(this.client, element, this.model.getDirection(), this.token); - if (!resolvedItem) { - return undefined; - } - element.parents = resolvedItem.parents; - if (element.parents.length === 0) { - this.emitter.fire(element); - } - } - return element.parents; } - return undefined; - } - - private static isWhiteListType(item: TypeHierarchyItem): boolean { - if (item.name === "Object" && item.detail === "java.lang") { - return true; - } - return false; - } - - private static getFakeItem(item: TypeHierarchyItem): TypeHierarchyItem { - let message: string; - if (item.name === "Object" && item.detail === "java.lang") { - message = "All classes are subtypes of java.lang.Object."; - } - return { - name: message, - kind: undefined, - children: [], - parents: [], - detail: undefined, - uri: undefined, - range: undefined, - selectionRange: undefined, - data: undefined, - deprecated: false, - expand: false, + const params: TypeHierarchySubtypesParams = { + item: this.client.code2ProtocolConverter.asTypeHierarchyItem(element), }; + const children = await this.client.sendRequest(TypeHierarchySubtypesRequest.type, params, this.token); + const childrenCodeItems = []; + for (const child of children) { + childrenCodeItems.push(toCodeTypeHierarchyItem(this.client, child)); + } + return childrenCodeItems; } private static themeIconIds = [ diff --git a/src/typeHierarchy/protocol.ts b/src/typeHierarchy/protocol.ts index de4621147..4c373cfe5 100644 --- a/src/typeHierarchy/protocol.ts +++ b/src/typeHierarchy/protocol.ts @@ -1,35 +1,10 @@ import * as vscode from "vscode"; -import { Range, SymbolKind } from "vscode-languageclient"; -export enum TypeHierarchyDirection { - children, - parents, - both -} - -export class LSPTypeHierarchyItem { - name: string; - detail: string; - kind: SymbolKind; - deprecated: boolean; - uri: string; - range: Range; - selectionRange: Range; - parents: LSPTypeHierarchyItem[]; - children: LSPTypeHierarchyItem[]; - data: any; -} +export const showTypeHierarchyReferenceViewCommand = "references-view.showTypeHierarchy"; +export const showSupertypeHierarchyReferenceViewCommand = "references-view.showSupertypes"; +export const showSubtypeHierarchyReferenceViewCommand = "references-view.showSubtypes"; -export class TypeHierarchyItem { - name: string; - detail: string; - kind: vscode.SymbolKind; - deprecated: boolean; - uri: string; - range: vscode.Range; - selectionRange: vscode.Range; - parents: TypeHierarchyItem[]; - children: TypeHierarchyItem[]; +export class CodeTypeHierarchyItem extends vscode.TypeHierarchyItem { + children?: CodeTypeHierarchyItem[]; data: any; - expand: boolean; } diff --git a/src/typeHierarchy/references-view.d.ts b/src/typeHierarchy/references-view.d.ts index e61177b90..73ed59e73 100644 --- a/src/typeHierarchy/references-view.d.ts +++ b/src/typeHierarchy/references-view.d.ts @@ -27,6 +27,13 @@ export interface SymbolTree { * @param input A symbol tree input object */ setInput(input: SymbolTreeInput): void; + + /** + * Get the contents of the references viewlet. + * + * @returns The current symbol tree input object + */ + getInput(): SymbolTreeInput | undefined; } /** @@ -99,6 +106,11 @@ export interface SymbolTreeModel { */ highlights?: SymbolItemEditorHighlights; + /** + * Optional support for drag and drop. + */ + dnd?: SymbolItemDragAndDrop; + /** * Optional dispose function which is invoked when this model is * needed anymore @@ -137,3 +149,8 @@ export interface SymbolItemEditorHighlights { */ getEditorHighlights(item: T, uri: vscode.Uri): vscode.Range[] | undefined; } + +export interface SymbolItemDragAndDrop { + + getDragUri(item: T): vscode.Uri | undefined; +} diff --git a/src/typeHierarchy/typeHierarchyTree.ts b/src/typeHierarchy/typeHierarchyTree.ts index d2870de6b..f2cbc658f 100644 --- a/src/typeHierarchy/typeHierarchyTree.ts +++ b/src/typeHierarchy/typeHierarchyTree.ts @@ -1,21 +1,18 @@ import * as vscode from "vscode"; -import { Position, TextDocumentIdentifier, TextDocumentPositionParams } from "vscode-languageclient"; +import { Position, Proposed, TextDocumentIdentifier, TextDocumentPositionParams, TypeHierarchyItem, TypeHierarchyPrepareRequest } from "vscode-languageclient"; import { LanguageClient } from "vscode-languageclient/node"; -import { Commands } from "../commands"; import { getActiveLanguageClient } from "../extension"; import { showNoLocationFound } from "../standardLanguageClient"; import { TypeHierarchyTreeInput } from "./model"; -import { LSPTypeHierarchyItem, TypeHierarchyDirection, TypeHierarchyItem } from "./protocol"; -import { SymbolTree } from "./references-view"; -import { toTypeHierarchyItem } from "./util"; +import { CodeTypeHierarchyItem } from "./protocol"; +import { SymbolTree, SymbolTreeInput } from "./references-view"; +import { toCodeTypeHierarchyItem } from "./util"; export class TypeHierarchyTree { private api: SymbolTree; - private direction: TypeHierarchyDirection; private client: LanguageClient; private cancelTokenSource: vscode.CancellationTokenSource; - private location: vscode.Location; - private baseItem: TypeHierarchyItem; + private anchor: vscode.Location; public initialized: boolean; constructor() { @@ -32,13 +29,16 @@ export class TypeHierarchyTree { this.initialized = true; } - public async setTypeHierarchy(location: vscode.Location, direction: TypeHierarchyDirection): Promise { + public async setTypeHierarchy(location: vscode.Location): Promise { if (!this.initialized) { await this.initialize(); } if (!this.api) { return; } + if (!this.isValidRequestPosition(location.uri, location.range.start)) { + return; + } if (this.cancelTokenSource) { this.cancelTokenSource.cancel(); } @@ -49,60 +49,46 @@ export class TypeHierarchyTree { textDocument: textDocument, position: position, }; - let lspItem: LSPTypeHierarchyItem; + let protocolItems: TypeHierarchyItem[]; try { - lspItem = await vscode.commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.OPEN_TYPE_HIERARCHY, JSON.stringify(params), JSON.stringify(direction), JSON.stringify(0), this.cancelTokenSource.token); + protocolItems = await this.client.sendRequest(TypeHierarchyPrepareRequest.type, params, this.cancelTokenSource.token); } catch (e) { // operation cancelled return; } - if (!lspItem) { - showNoLocationFound('No Type Hierarchy found'); - return; + if (!protocolItems || protocolItems.length === 0) { + throw new Error("No Class Hierarchy found"); } - const symbolKind = this.client.protocol2CodeConverter.asSymbolKind(lspItem.kind); - if (direction === TypeHierarchyDirection.both && symbolKind === vscode.SymbolKind.Interface) { - direction = TypeHierarchyDirection.children; + const protocolItem = protocolItems[0]; + const symbolKind = this.client.protocol2CodeConverter.asSymbolKind(protocolItem.kind); + if (symbolKind === vscode.SymbolKind.Interface) { + throw new Error("Class Hierarchy is not available for interfaces"); } - const item: TypeHierarchyItem = toTypeHierarchyItem(this.client, lspItem, direction); - const input: TypeHierarchyTreeInput = new TypeHierarchyTreeInput(location, direction, this.cancelTokenSource.token, item); - this.location = location; - this.direction = direction; - this.baseItem = item; + const item: CodeTypeHierarchyItem = toCodeTypeHierarchyItem(this.client, protocolItem); + const input: TypeHierarchyTreeInput = new TypeHierarchyTreeInput(location, this.cancelTokenSource.token, item); + this.anchor = input.location; this.api.setInput(input); } - public changeDirection(direction: TypeHierarchyDirection): void { + public async setTypeHierarchyFromReferenceView(): Promise { + if (!this.initialized) { + await this.initialize(); + } if (!this.api) { return; } - if (this.cancelTokenSource) { - this.cancelTokenSource.cancel(); - } - this.cancelTokenSource = new vscode.CancellationTokenSource(); - this.baseItem.children = undefined; - this.baseItem.parents = undefined; - const input: TypeHierarchyTreeInput = new TypeHierarchyTreeInput(this.location, direction, this.cancelTokenSource.token, this.baseItem); - this.direction = direction; - this.api.setInput(input); - } - - public async changeBaseItem(item: TypeHierarchyItem): Promise { - if (!this.api) { + const treeInput: SymbolTreeInput = this.api.getInput(); + if (!treeInput) { return; } - if (this.cancelTokenSource) { - this.cancelTokenSource.cancel(); + const location = treeInput.location; + if (location) { + await this.setTypeHierarchy(location); } - this.cancelTokenSource = new vscode.CancellationTokenSource(); - item.parents = undefined; - item.children = undefined; - const location: vscode.Location = new vscode.Location(vscode.Uri.parse(item.uri), item.selectionRange); - const newLocation: vscode.Location = (await this.isValidRequestPosition(location.uri, location.range.start)) ? location : this.location; - const input: TypeHierarchyTreeInput = new TypeHierarchyTreeInput(newLocation, this.direction, this.cancelTokenSource.token, item); - this.location = newLocation; - this.baseItem = item; - this.api.setInput(input); + } + + public getAnchor(): vscode.Location | undefined { + return this.anchor; } private async isValidRequestPosition(uri: vscode.Uri, position: vscode.Position) { diff --git a/src/typeHierarchy/util.ts b/src/typeHierarchy/util.ts index 2f3463780..20999c6c8 100644 --- a/src/typeHierarchy/util.ts +++ b/src/typeHierarchy/util.ts @@ -1,119 +1,31 @@ -import { CancellationToken, commands, SymbolKind } from "vscode"; -import { LanguageClient } from "vscode-languageclient/node"; -import { Commands } from "../commands"; -import { LSPTypeHierarchyItem, TypeHierarchyDirection, TypeHierarchyItem } from "./protocol"; +import { CancellationToken, SymbolKind } from "vscode"; +import { LanguageClient, TypeHierarchyItem, TypeHierarchySupertypesRequest } from "vscode-languageclient/node"; +import { CodeTypeHierarchyItem } from "./protocol"; -export function toSingleLSPTypeHierarchyItem(client: LanguageClient, typeHierarchyItem: TypeHierarchyItem): LSPTypeHierarchyItem { - if (!typeHierarchyItem) { - return undefined; - } - return { - name: typeHierarchyItem.name, - detail: typeHierarchyItem.detail, - kind: client.code2ProtocolConverter.asSymbolKind(typeHierarchyItem.kind), - deprecated: typeHierarchyItem.deprecated, - uri: typeHierarchyItem.uri, - range: client.code2ProtocolConverter.asRange(typeHierarchyItem.range), - selectionRange: client.code2ProtocolConverter.asRange(typeHierarchyItem.selectionRange), - parents: undefined, - children: undefined, - data: typeHierarchyItem.data, - }; -} - -export function toTypeHierarchyItem(client: LanguageClient, lspTypeHierarchyItem: LSPTypeHierarchyItem, direction: TypeHierarchyDirection): TypeHierarchyItem { - if (!lspTypeHierarchyItem) { - return undefined; - } - let parents: TypeHierarchyItem[]; - let children: TypeHierarchyItem[]; - if (direction === TypeHierarchyDirection.parents || direction === TypeHierarchyDirection.both) { - if (lspTypeHierarchyItem.parents) { - parents = []; - for (const parent of lspTypeHierarchyItem.parents) { - parents.push(toTypeHierarchyItem(client, parent, TypeHierarchyDirection.parents)); - } - parents = parents.sort((a, b) => { - return (a.kind.toString() === b.kind.toString()) ? a.name.localeCompare(b.name) : b.kind.toString().localeCompare(a.kind.toString()); - }); - } - } - if (direction === TypeHierarchyDirection.children || direction === TypeHierarchyDirection.both) { - if (lspTypeHierarchyItem.children) { - children = []; - for (const child of lspTypeHierarchyItem.children) { - children.push(toTypeHierarchyItem(client, child, TypeHierarchyDirection.children)); - } - children = children.sort((a, b) => { - return (a.kind.toString() === b.kind.toString()) ? a.name.localeCompare(b.name) : b.kind.toString().localeCompare(a.kind.toString()); - }); - } - } - return { - name: lspTypeHierarchyItem.name, - detail: lspTypeHierarchyItem.detail, - kind: client.protocol2CodeConverter.asSymbolKind(lspTypeHierarchyItem.kind), - deprecated: lspTypeHierarchyItem.deprecated, - uri: lspTypeHierarchyItem.uri, - range: client.protocol2CodeConverter.asRange(lspTypeHierarchyItem.range), - selectionRange: client.protocol2CodeConverter.asRange(lspTypeHierarchyItem.selectionRange), - parents: parents, - children: children, - data: lspTypeHierarchyItem.data, - expand: false, - }; -} - -export function typeHierarchyDirectionToContextString(direction: TypeHierarchyDirection): string { - switch (direction) { - case TypeHierarchyDirection.children: - return "children"; - case TypeHierarchyDirection.parents: - return "parents"; - case TypeHierarchyDirection.both: - return "both"; - default: - return undefined; - } -} - -export async function resolveTypeHierarchy(client: LanguageClient, typeHierarchyItem: TypeHierarchyItem, direction: TypeHierarchyDirection, token: CancellationToken): Promise { - const lspTypeHierarchyItem = toSingleLSPTypeHierarchyItem(client, typeHierarchyItem); - let resolvedLSPItem: LSPTypeHierarchyItem; - try { - resolvedLSPItem = await commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND, Commands.RESOLVE_TYPE_HIERARCHY, JSON.stringify(lspTypeHierarchyItem), JSON.stringify(direction), JSON.stringify(1), token); - } catch (e) { - // operation cancelled - return undefined; - } - const resolvedItem = toTypeHierarchyItem(client, resolvedLSPItem, direction); - if (!resolvedItem) { +export function toCodeTypeHierarchyItem(client: LanguageClient, item: TypeHierarchyItem): CodeTypeHierarchyItem { + if (!item) { return undefined; } - resolvedItem.expand = typeHierarchyItem.expand; - return resolvedItem; + const codeItem = client.protocol2CodeConverter.asTypeHierarchyItem(item); + return codeItem as CodeTypeHierarchyItem; } -export async function getRootItem(client: LanguageClient, typeHierarchyItem: TypeHierarchyItem, token: CancellationToken): Promise { +export async function getRootItem(client: LanguageClient, typeHierarchyItem: CodeTypeHierarchyItem, token: CancellationToken): Promise { if (!typeHierarchyItem) { return undefined; } - if (!typeHierarchyItem.parents) { - const resolvedItem = await resolveTypeHierarchy(client, typeHierarchyItem, TypeHierarchyDirection.parents, token); - if (!resolvedItem || !resolvedItem.parents) { - return typeHierarchyItem; - } else { - typeHierarchyItem.parents = resolvedItem.parents; - } - } - if (typeHierarchyItem.parents.length === 0) { + const supertypeItems = await client.sendRequest(TypeHierarchySupertypesRequest.type, { + item: client.code2ProtocolConverter.asTypeHierarchyItem(typeHierarchyItem) + }, token); + if (supertypeItems.length === 0) { return typeHierarchyItem; } else { - for (const parent of typeHierarchyItem.parents) { - if (parent.kind === SymbolKind.Class || parent.kind === SymbolKind.Null) { - parent.children = [typeHierarchyItem]; - parent.expand = true; - return getRootItem(client, parent, token); + for (const supertypeItem of supertypeItems) { + const symbolKind = client.protocol2CodeConverter.asSymbolKind(supertypeItem.kind); + if (symbolKind === SymbolKind.Class || symbolKind === SymbolKind.Null) { + const codeItem = toCodeTypeHierarchyItem(client, supertypeItem); + codeItem.children = [typeHierarchyItem]; + return getRootItem(client, codeItem, token); } } return typeHierarchyItem; diff --git a/test/standard-mode-suite/extension.test.ts b/test/standard-mode-suite/extension.test.ts index b325d0250..60500fb45 100644 --- a/test/standard-mode-suite/extension.test.ts +++ b/test/standard-mode-suite/extension.test.ts @@ -46,7 +46,6 @@ suite('Java Language Extension - Standard', () => { Commands.APPLY_REFACTORING_COMMAND, Commands.APPLY_WORKSPACE_EDIT, Commands.BUILD_PROJECT, - Commands.CHANGE_BASE_TYPE, Commands.CHOOSE_IMPORTS, Commands.CLEAN_SHARED_INDEXES, Commands.CLEAN_WORKSPACE, @@ -82,7 +81,6 @@ suite('Java Language Extension - Standard', () => { Commands.OPEN_SERVER_LOG, Commands.OPEN_SERVER_STDOUT_LOG, Commands.OPEN_SERVER_STDERR_LOG, - Commands.OPEN_TYPE_HIERARCHY, Commands.ORGANIZE_IMPORTS, Commands.ORGANIZE_IMPORTS_SILENTLY, Commands.OVERRIDE_METHODS_PROMPT, @@ -93,18 +91,17 @@ suite('Java Language Extension - Standard', () => { Commands.REMOVE_FROM_SOURCEPATH_CMD, Commands.RENAME_COMMAND, "java.project.resolveStackTraceLocation", - Commands.RESOLVE_TYPE_HIERARCHY, Commands.RESOLVE_WORKSPACE_SYMBOL, Commands.RUNTIME_VALIDATION_OPEN, + Commands.SHOW_CLASS_HIERARCHY, + Commands.SHOW_CLASS_HIERARCHY_FROM_REFERENCE_VIEW, Commands.SHOW_JAVA_IMPLEMENTATIONS, Commands.SHOW_JAVA_REFERENCES, Commands.SHOW_SERVER_TASK_STATUS, - Commands.SWITCH_SERVER_MODE, - "java.edit.stringFormatting", - Commands.SHOW_TYPE_HIERARCHY, Commands.SHOW_SUBTYPE_HIERARCHY, Commands.SHOW_SUPERTYPE_HIERARCHY, - Commands.SHOW_CLASS_HIERARCHY, + Commands.SWITCH_SERVER_MODE, + "java.edit.stringFormatting", Commands.UPGRADE_GRADLE_WRAPPER, Commands.UPDATE_SOURCE_ATTACHMENT, Commands.UPDATE_SOURCE_ATTACHMENT_CMD,