From 67c97944928dc65ab3aea2a112993d8f8cd3acf1 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 | 34 +--- package.nls.json | 1 - package.nls.zh.json | 1 - src/commands.ts | 18 +- src/extension.ts | 4 +- src/refactorAction.ts | 2 +- src/standardLanguageClient.ts | 29 ++- src/typeHierarchy/model.reference-view.ts | 214 +++++++++++++++++++++ src/typeHierarchy/model.ts | 199 +++++-------------- src/typeHierarchy/protocol.ts | 33 +--- src/typeHierarchy/references-view.d.ts | 10 + src/typeHierarchy/typeHierarchyTree.ts | 77 ++------ src/typeHierarchy/util.ts | 123 ++---------- test/standard-mode-suite/extension.test.ts | 4 +- 14 files changed, 325 insertions(+), 424 deletions(-) create mode 100644 src/typeHierarchy/model.reference-view.ts diff --git a/package.json b/package.json index 73e95b8cb1..46e537fb94 100644 --- a/package.json +++ b/package.json @@ -941,35 +941,22 @@ "title": "%java.action.navigateToSuperImplementation%", "category": "Java" }, - { - "command": "java.action.showTypeHierarchy", - "title": "%java.action.showTypeHierarchy%", - "category": "Java" - }, { "command": "java.action.showClassHierarchy", "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" } ], @@ -1025,7 +1012,7 @@ "group": "navigation@90" }, { - "command": "java.action.showTypeHierarchy", + "command": "java.action.showClassHierarchy", "when": "javaLSReady && editorTextFocus && editorLangId == java", "group": "0_navigation@3" } @@ -1051,13 +1038,9 @@ "command": "java.project.listSourcePaths.command", "when": "javaLSReady" }, - { - "command": "java.action.showTypeHierarchy", - "when": "javaLSReady && editorIsOpen" - }, { "command": "java.action.showClassHierarchy", - "when": "false" + "when": "javaLSReady && editorIsOpen" }, { "command": "java.action.showSubtypeHierarchy", @@ -1067,10 +1050,6 @@ "command": "java.action.showSupertypeHierarchy", "when": "false" }, - { - "command": "java.action.changeBaseType", - "when": "false" - }, { "command": "java.project.updateSourceAttachment.command", "when": "false" @@ -1096,7 +1075,7 @@ { "command": "java.action.showClassHierarchy", "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 && typeHierarchySymbolKind != 10" }, { "command": "java.action.showSupertypeHierarchy", @@ -1108,13 +1087,6 @@ "group": "navigation@1", "when": "view == references-view.tree && reference-list.hasResult && reference-list.source == javaTypeHierarchy" } - ], - "view/item/context": [ - { - "command": "java.action.changeBaseType", - "group": "1", - "when": "view == references-view.tree && reference-list.hasResult && reference-list.source == javaTypeHierarchy && viewItem != 'false'" - } ] } }, diff --git a/package.nls.json b/package.nls.json index 622212e44b..4d466d33db 100644 --- a/package.nls.json +++ b/package.nls.json @@ -14,7 +14,6 @@ "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", diff --git a/package.nls.zh.json b/package.nls.zh.json index 90a57d32df..190d83ef3a 100644 --- a/package.nls.zh.json +++ b/package.nls.zh.json @@ -14,7 +14,6 @@ "java.project.listSourcePaths": "列出所有 Java 源代码路径", "java.show.server.task.status": "显示工作状态", "java.action.navigateToSuperImplementation": "转到父类实现", - "java.action.showTypeHierarchy": "显示类型层次结构", "java.action.showClassHierarchy": "显示类的继承关系", "java.action.showSupertypeHierarchy": "显示父类层次结构", "java.action.showSubtypeHierarchy": "显示子类层次结构", diff --git a/src/commands.ts b/src/commands.ts index 300dcc84d5..b4b34540dd 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -192,10 +192,6 @@ export namespace Commands { * Navigate To Super Method Command. */ 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. */ @@ -205,21 +201,9 @@ export namespace Commands { */ export const SHOW_SUBTYPE_HIERARCHY = 'java.action.showSubtypeHierarchy'; /** - * Show Type Hierarchy of given Selection. + * Show Class Hierarchy of given Selection. */ 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. - */ - export const RESOLVE_TYPE_HIERARCHY = 'java.navigate.resolveTypeHierarchy'; /** * Show server task status */ diff --git a/src/extension.ts b/src/extension.ts index e08c7e12e1..9d46a58a6b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -284,7 +284,7 @@ export function activate(context: ExtensionContext): Promise { } const codeActionContext: CodeActionContext = { diagnostics: allDiagnostics, - triggerKind: CodeActionTriggerKind?.Automatic, + triggerKind: context.triggerKind, only: context.only, }; params.context = client.code2ProtocolConverter.asCodeActionContext(codeActionContext); @@ -304,7 +304,7 @@ export function activate(context: ExtensionContext): Promise { } } return result; - }, (error) => { + }, () => { return Promise.resolve([]); }); } diff --git a/src/refactorAction.ts b/src/refactorAction.ts index 65123bc496..69c4f47c2e 100644 --- a/src/refactorAction.ts +++ b/src/refactorAction.ts @@ -214,7 +214,7 @@ async function applyRefactorEdit(languageClient: LanguageClient, refactorEdit: R } if (refactorEdit.edit) { - const edit = await languageClient.protocol2CodeConverter.asWorkspaceEdit(refactorEdit.edit); + const edit = languageClient.protocol2CodeConverter.asWorkspaceEdit(refactorEdit.edit); if (edit) { await workspace.applyEdit(edit); } diff --git a/src/standardLanguageClient.ts b/src/standardLanguageClient.ts index adef14c1a4..556c72969e 100644 --- a/src/standardLanguageClient.ts +++ b/src/standardLanguageClient.ts @@ -1,6 +1,6 @@ 'use strict'; -import { ExtensionContext, window, workspace, commands, Uri, ProgressLocation, ViewColumn, EventEmitter, extensions, Location, languages, CodeActionKind, TextEditor, CancellationToken, ConfigurationTarget, Range, Position } from "vscode"; +import { ExtensionContext, window, workspace, commands, Uri, ProgressLocation, ViewColumn, EventEmitter, extensions, Location, languages, CodeActionKind, TextEditor, CancellationToken, ConfigurationTarget, Range, Position, TypeHierarchyItem } from "vscode"; import { Commands } from "./commands"; import { serverStatus, ServerStatusKind } from "./serverStatus"; import { prepareExecutable, awaitServerConnection } from "./javaServerStarter"; @@ -29,10 +29,11 @@ import * as fileEventHandler from './fileEventHandler'; import { markdownPreviewProvider } from "./markdownPreviewProvider"; import { RefactorDocumentProvider, javaRefactorKinds } from "./codeActionProvider"; import { typeHierarchyTree } from "./typeHierarchy/typeHierarchyTree"; -import { TypeHierarchyDirection, TypeHierarchyItem } from "./typeHierarchy/protocol"; import { buildFilePatterns } from './plugin'; import { pomCodeActionMetadata, PomCodeActionProvider } from "./pom/pomCodeActionProvider"; import { findRuntimes, IJavaRuntime } from "jdk-utils"; +import { TypeHierarchyFeature } from "vscode-languageclient/lib/common/proposed.typeHierarchy"; +import { TypeHierarchyDirection, TypeItem, TypesModel } from "./typeHierarchy/model.reference-view"; const extensionName = 'Language Support for Java'; const GRADLE_CHECKSUM = "gradle/checksum/prompt"; @@ -98,7 +99,7 @@ export class StandardLanguageClient { // Create the language client and start the client. this.languageClient = new LanguageClient('java', extensionName, serverOptions, clientOptions); - + this.languageClient.registerFeature(new TypeHierarchyFeature(this.languageClient)); this.languageClient.onReady().then(() => { activationProgressNotification.showProgress(); this.languageClient.onNotification(StatusNotification.type, (report) => { @@ -371,31 +372,27 @@ export class StandardLanguageClient { } })); - context.subscriptions.push(commands.registerCommand(Commands.SHOW_TYPE_HIERARCHY, (location: any) => { + context.subscriptions.push(commands.registerCommand(Commands.SHOW_CLASS_HIERARCHY, (location: any) => { if (location instanceof Uri) { - typeHierarchyTree.setTypeHierarchy(new Location(location, window.activeTextEditor.selection.active), TypeHierarchyDirection.Both); + typeHierarchyTree.setTypeHierarchy(new Location(location, window.activeTextEditor.selection.active)); } else { if (window.activeTextEditor?.document?.languageId !== "java") { return; } - typeHierarchyTree.setTypeHierarchy(new Location(window.activeTextEditor.document.uri, window.activeTextEditor.selection.active), TypeHierarchyDirection.Both); + typeHierarchyTree.setTypeHierarchy(new Location(window.activeTextEditor.document.uri, window.activeTextEditor.selection.active)); } })); - 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); + const typesModel: TypesModel = new TypesModel(TypeHierarchyDirection.Supertypes, [typeHierarchyTree.currentItem]); + const typeItem: TypeItem = new TypeItem(typesModel, typeHierarchyTree.currentItem, undefined); + commands.executeCommand("references-view.showSupertypes", typeItem); })); context.subscriptions.push(commands.registerCommand(Commands.SHOW_SUBTYPE_HIERARCHY, () => { - typeHierarchyTree.changeDirection(TypeHierarchyDirection.Children); - })); - - context.subscriptions.push(commands.registerCommand(Commands.CHANGE_BASE_TYPE, async (item: TypeHierarchyItem) => { - typeHierarchyTree.changeBaseItem(item); + const typesModel: TypesModel = new TypesModel(TypeHierarchyDirection.Subtypes, [typeHierarchyTree.currentItem]); + const typeItem: TypeItem = new TypeItem(typesModel, typeHierarchyTree.currentItem, undefined); + commands.executeCommand("references-view.showSubtypes", typeItem); })); context.subscriptions.push(commands.registerCommand(Commands.COMPILE_WORKSPACE, (isFullCompile: boolean, token?: CancellationToken) => { diff --git a/src/typeHierarchy/model.reference-view.ts b/src/typeHierarchy/model.reference-view.ts new file mode 100644 index 0000000000..e93ed89597 --- /dev/null +++ b/src/typeHierarchy/model.reference-view.ts @@ -0,0 +1,214 @@ +import * as vscode from 'vscode'; +import { SymbolItemDragAndDrop, SymbolItemEditorHighlights, SymbolItemNavigation, SymbolTreeInput } from './references-view'; + +export class TypesTreeInput implements SymbolTreeInput { + + readonly title: string; + readonly contextValue: string = 'typeHierarchy'; + + constructor( + readonly location: vscode.Location, + readonly direction: TypeHierarchyDirection, + ) { + this.title = direction === TypeHierarchyDirection.Supertypes + ? 'Supertypes Of' + : 'Subtypes Of'; + } + + async resolve() { + + const items = await Promise.resolve(vscode.commands.executeCommand('vscode.prepareTypeHierarchy', this.location.uri, this.location.range.start)); + const model = new TypesModel(this.direction, items ?? []); + const provider = new TypeItemDataProvider(model); + + if (model.roots.length === 0) { + return; + } + + return { + provider, + get message() { return model.roots.length === 0 ? 'No results.' : undefined; }, + navigation: model, + dispose() { + provider.dispose(); + } + }; + } + + with(location: vscode.Location): TypesTreeInput { + return new TypesTreeInput(location, this.direction); + } +} + +export const enum TypeHierarchyDirection { + Subtypes = 'subtypes', + Supertypes = 'supertypes' +} + +export class TypeItem { + + children?: TypeItem[]; + + constructor( + readonly model: TypesModel, + readonly item: vscode.TypeHierarchyItem, + readonly parent: TypeItem | undefined, + ) { } + + remove(): void { + this.model.remove(this); + } +} + +export class TypesModel implements SymbolItemNavigation, SymbolItemEditorHighlights, SymbolItemDragAndDrop { + + readonly roots: TypeItem[] = []; + + private readonly _onDidChange = new vscode.EventEmitter(); + readonly onDidChange = this._onDidChange.event; + + constructor(readonly direction: TypeHierarchyDirection, items: vscode.TypeHierarchyItem[]) { + this.roots = items.map(item => new TypeItem(this, item, undefined)); + } + + private async _resolveTypes(currentType: TypeItem): Promise { + if (this.direction === TypeHierarchyDirection.Supertypes) { + const types = await vscode.commands.executeCommand('vscode.provideSupertypes', currentType.item); + return types ? types.map(item => new TypeItem(this, item, currentType)) : []; + } else { + const types = await vscode.commands.executeCommand('vscode.provideSubtypes', currentType.item); + return types ? types.map(item => new TypeItem(this, item, currentType)) : []; + } + } + + async getTypeChildren(item: TypeItem): Promise { + if (!item.children) { + item.children = await this._resolveTypes(item); + } + return item.children; + } + + // -- dnd + + getDragUri(item: TypeItem): vscode.Uri | undefined { + return asResourceUrl(item.item.uri, item.item.range); + } + + // -- navigation + + location(currentType: TypeItem) { + return new vscode.Location(currentType.item.uri, currentType.item.range); + } + + nearest(uri: vscode.Uri, _position: vscode.Position): TypeItem | undefined { + return this.roots.find(item => item.item.uri.toString() === uri.toString()) ?? this.roots[0]; + } + + next(from: TypeItem): TypeItem { + return this._move(from, true) ?? from; + } + + previous(from: TypeItem): TypeItem { + return this._move(from, false) ?? from; + } + + private _move(item: TypeItem, fwd: boolean) { + if (item.children?.length) { + return fwd ? item.children[0] : tail(item.children); + } + const array = this.roots.includes(item) ? this.roots : item.parent?.children; + if (array?.length) { + const idx = array.indexOf(item); + const delta = fwd ? 1 : -1; + return array[idx + delta + array.length % array.length]; + } + } + + // --- highlights + + getEditorHighlights(currentType: TypeItem, uri: vscode.Uri): vscode.Range[] | undefined { + return currentType.item.uri.toString() === uri.toString() ? [currentType.item.selectionRange] : undefined; + } + + remove(item: TypeItem) { + const isInRoot = this.roots.includes(item); + const siblings = isInRoot ? this.roots : item.parent?.children; + if (siblings) { + del(siblings, item); + this._onDidChange.fire(this); + } + } +} + +class TypeItemDataProvider implements vscode.TreeDataProvider { + + private readonly _emitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData = this._emitter.event; + + private readonly _modelListener: vscode.Disposable; + + constructor(private _model: TypesModel) { + this._modelListener = _model.onDidChange(e => this._emitter.fire(e instanceof TypeItem ? e : undefined)); + } + + dispose(): void { + this._emitter.dispose(); + this._modelListener.dispose(); + } + + getTreeItem(element: TypeItem): vscode.TreeItem { + + const item = new vscode.TreeItem(element.item.name); + item.description = element.item.detail; + item.contextValue = 'type-item'; + item.iconPath = TypeItemDataProvider.getThemeIcon(element.item.kind); + item.command = { + command: 'vscode.open', + title: 'Open Type', + arguments: [ + element.item.uri, + { selection: element.item.selectionRange.with({ end: element.item.selectionRange.start }) } + ] + }; + item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed; + return item; + } + + getChildren(element?: TypeItem | undefined) { + return element + ? this._model.getTypeChildren(element) + : this._model.roots; + } + + getParent(element: TypeItem) { + return element.parent; + } + + 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 = TypeItemDataProvider.themeIconIds[kind]; + return id ? new vscode.ThemeIcon(id) : undefined; + } +} + +function del(array: T[], e: T): void { + const idx = array.indexOf(e); + if (idx >= 0) { + array.splice(idx, 1); + } +} + +function tail(array: T[]): T | undefined { + return array[array.length - 1]; +} + +function asResourceUrl(uri: vscode.Uri, range: vscode.Range): vscode.Uri { + return uri.with({ fragment: `L${1 + range.start.line},${1 + range.start.character}-${1 + range.end.line},${1 + range.end.character}` }); +} \ No newline at end of file diff --git a/src/typeHierarchy/model.ts b/src/typeHierarchy/model.ts index f200751caa..f1bcca6531 100644 --- a/src/typeHierarchy/model.ts +++ b/src/typeHierarchy/model.ts @@ -1,48 +1,32 @@ 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 } 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; - switch (direction) { - case TypeHierarchyDirection.Both: - this.title = "Class Hierarchy"; - break; - case TypeHierarchyDirection.Parents: - this.title = "Supertype Hierarchy"; - break; - case TypeHierarchyDirection.Children: - this.title = "Subtype Hierarchy"; - break; - default: - return; - } + this.title = "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, @@ -50,60 +34,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.file(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 { @@ -111,7 +87,7 @@ class TypeHierarchyTreeDataProvider implements vscode.TreeDataProvider { + async getTreeItem(element: CodeTypeHierarchyItem): Promise { if (!element) { return undefined; } @@ -123,124 +99,37 @@ class TypeHierarchyTreeDataProvider implements vscode.TreeDataProvider{ 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}${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 lspItem = this.client.code2ProtocolConverter.asTypeHierarchyItem(element); + lspItem.data = element.data; + const params: Proposed.TypeHierarchySubtypesParams = { + item: lspItem, }; + const children = await this.client.sendRequest(Proposed.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 1572091747..375d5550ef 100644 --- a/src/typeHierarchy/protocol.ts +++ b/src/typeHierarchy/protocol.ts @@ -1,35 +1,6 @@ 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 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 afea1fe3c6..32d05bf887 100644 --- a/src/typeHierarchy/references-view.d.ts +++ b/src/typeHierarchy/references-view.d.ts @@ -99,6 +99,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 +142,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 5e1e252eec..5168f6933a 100644 --- a/src/typeHierarchy/typeHierarchyTree.ts +++ b/src/typeHierarchy/typeHierarchyTree.ts @@ -1,22 +1,19 @@ import * as vscode from "vscode"; -import { Position, TextDocumentIdentifier, TextDocumentPositionParams } from "vscode-languageclient"; +import { Position, Proposed, TextDocumentIdentifier, TextDocumentPositionParams, TypeHierarchyItem } 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 { CodeTypeHierarchyItem } from "./protocol"; import { SymbolTree } from "./references-view"; -import { ToTypeHierarchyItem } from "./util"; +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; public initialized: boolean; + public currentItem: vscode.TypeHierarchyItem; constructor() { this.initialized = false; @@ -28,7 +25,7 @@ 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(); } @@ -45,70 +42,28 @@ 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(Proposed.TypeHierarchyPrepareRequest.type, params, this.cancelTokenSource.token); } catch (e) { // operation cancelled return; } - if (!lspItem) { - showNoLocationFound('No Type Hierarchy found'); + if (!protocolItems || protocolItems.length === 0) { + showNoLocationFound('No Class Hierarchy found'); return; } - const symbolKind = this.client.protocol2CodeConverter.asSymbolKind(lspItem.kind); - if (direction === TypeHierarchyDirection.Both && symbolKind === vscode.SymbolKind.Interface) { - direction = TypeHierarchyDirection.Children; - } - 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; - this.api.setInput(input); - } - - public changeDirection(direction: TypeHierarchyDirection): void { - 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 protocolItem = protocolItems[0]; + this.currentItem = this.client.protocol2CodeConverter.asTypeHierarchyItem(protocolItem); + const symbolKind = this.client.protocol2CodeConverter.asSymbolKind(protocolItem.kind); + if (symbolKind === vscode.SymbolKind.Interface) { + showNoLocationFound('No Class Hierarchy found'); return; } - if (this.cancelTokenSource) { - this.cancelTokenSource.cancel(); - } - 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; + const item: CodeTypeHierarchyItem = ToCodeTypeHierarchyItem(this.client, protocolItem); + const input: TypeHierarchyTreeInput = new TypeHierarchyTreeInput(location, this.cancelTokenSource.token, item); this.api.setInput(input); } - - private async isValidRequestPosition(uri: vscode.Uri, position: vscode.Position) { - const doc = await vscode.workspace.openTextDocument(uri); - let range = doc.getWordRangeAtPosition(position); - if (!range) { - range = doc.getWordRangeAtPosition(position, /[^\s]+/); - } - return Boolean(range); - } } export const typeHierarchyTree: TypeHierarchyTree = new TypeHierarchyTree(); diff --git a/src/typeHierarchy/util.ts b/src/typeHierarchy/util.ts index 67135ad4d0..81e5f46505 100644 --- a/src/typeHierarchy/util.ts +++ b/src/typeHierarchy/util.ts @@ -1,119 +1,32 @@ import { CancellationToken, commands, SymbolKind } from "vscode"; -import { LanguageClient } from "vscode-languageclient/node"; -import { Commands } from "../commands"; -import { LSPTypeHierarchyItem, TypeHierarchyDirection, TypeHierarchyItem } from "./protocol"; +import { LanguageClient, Proposed, TypeHierarchyItem } 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 lspItem = client.code2ProtocolConverter.asTypeHierarchyItem(typeHierarchyItem); + lspItem.data = typeHierarchyItem.data; + const supertypeItems = await client.sendRequest(Proposed.TypeHierarchySupertypesRequest.type, { + item: lspItem + }, token); + if (supertypeItems.length === 0) { return typeHierarchyItem; } else { - for (const parent of typeHierarchyItem.parents) { - if (parent.kind === SymbolKind.Class) { - parent.children = [typeHierarchyItem]; - parent.expand = true; - return getRootItem(client, parent, token); + for (const supertypeItem of supertypeItems) { + if (client.protocol2CodeConverter.asSymbolKind(supertypeItem.kind) === SymbolKind.Class) { + 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 8d3d741a03..00efba535f 100644 --- a/test/standard-mode-suite/extension.test.ts +++ b/test/standard-mode-suite/extension.test.ts @@ -66,11 +66,9 @@ suite('Java Language Extension - Standard', () => { Commands.SWITCH_SERVER_MODE, Commands.UPDATE_SOURCE_ATTACHMENT_CMD, Commands.RUNTIME_VALIDATION_OPEN, - Commands.CHANGE_BASE_TYPE, - Commands.SHOW_TYPE_HIERARCHY, + Commands.SHOW_CLASS_HIERARCHY, Commands.SHOW_SUBTYPE_HIERARCHY, Commands.SHOW_SUPERTYPE_HIERARCHY, - Commands.SHOW_CLASS_HIERARCHY, ].sort(); const foundJavaCommands = commands.filter((value) => { return JAVA_COMMANDS.indexOf(value)>=0 || value.startsWith('java.');