move references-viewlet into extensions/-folder

This commit is contained in:
Johannes 2022-05-03 16:46:13 +02:00
parent e19f09713b
commit d3e6eb992b
No known key found for this signature in database
GPG key ID: 6DEF802A22264FCA
24 changed files with 2361 additions and 15 deletions

View file

@ -60,6 +60,7 @@ const compilations = [
'npm/tsconfig.json',
'php-language-features/tsconfig.json',
'search-result/tsconfig.json',
'references-view/tsconfig.json',
'simple-browser/tsconfig.json',
'typescript-language-features/test-workspace/tsconfig.json',
'typescript-language-features/tsconfig.json',

View file

@ -36,6 +36,7 @@ exports.dirs = [
'extensions/notebook-renderers',
'extensions/npm',
'extensions/php-language-features',
'extensions/references-view',
'extensions/search-result',
'extensions/simple-browser',
'extensions/typescript-language-features',

View file

@ -0,0 +1,6 @@
.vscode/**
src/**
out/**
tsconfig.json
*.webpack.config.js
yarn.lock

View file

@ -0,0 +1,34 @@
# References View
This extension shows reference search results as separate view, just like search results. It complements the peek view presentation that is also built into VS Code. The following feature are available:
* List All References via the Command Palette, the Context Menu, or via <kbd>Alt+Shift+F12</kbd>
* View references in a dedicated tree view that sits in the sidebar
* Navigate through search results via <kbd>F4</kbd> and <kbd>Shift+F4</kbd>
* Remove references from the list via inline commands
![](https://raw.githubusercontent.com/microsoft/vscode-references-view/master/media/demo.png)
**Note** that this extension is bundled with Visual Studio Code version 1.29 and later - it doesn't need to be installed anymore.
## Requirements
This extension is just an alternative UI for reference search and extensions implementing reference search must still be installed.
## Issues
This extension ships with Visual Studio Code and uses its issue tracker. Please file issue here: https://github.com/Microsoft/vscode/issues
# Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.microsoft.com.
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

View file

@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const withBrowserDefaults = require('../shared.webpack.config').browser;
const path = require('path');
module.exports = withBrowserDefaults({
context: __dirname,
entry: {
extension: './src/extension.ts'
},
output: {
filename: 'extension.js',
path: path.join(__dirname, 'dist')
}
});

View file

@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const withDefaults = require('../shared.webpack.config');
module.exports = withDefaults({
context: __dirname,
resolve: {
mainFields: ['module', 'main']
},
entry: {
extension: './src/extension.ts',
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -0,0 +1,411 @@
{
"name": "references-view",
"displayName": "Reference Search View",
"icon": "media/icon.png",
"description": "Reference Search results as separate, stable view in the sidebar",
"version": "0.0.90",
"publisher": "ms-vscode",
"engines": {
"vscode": "^1.67.0"
},
"capabilities": {
"virtualWorkspaces": true,
"untrustedWorkspaces": {
"supported": true
}
},
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/vscode-references-view"
},
"bugs": {
"url": "https://github.com/Microsoft/vscode-references-view/issues"
},
"categories": [
"Programming Languages"
],
"activationEvents": [
"onCommand:references-view.find",
"onCommand:references-view.findReferences",
"onCommand:references-view.findImplementations",
"onCommand:references-view.showCallHierarchy",
"onCommand:references-view.showTypeHierarchy",
"onCommand:editor.action.showReferences",
"onView:references-view.tree"
],
"main": "./out/extension",
"browser": "./dist/extension.js",
"contributes": {
"configuration": {
"properties": {
"references.preferredLocation": {
"description": "Controls whether 'Peek References' or 'Find References' is invoked when selecting code lens references",
"type": "string",
"default": "peek",
"enum": [
"peek",
"view"
],
"enumDescriptions": [
"Show references in peek editor.",
"Show references in separate view."
]
}
}
},
"viewsContainers": {
"activitybar": [
{
"id": "references-view",
"icon": "$(references)",
"title": "References"
}
]
},
"views": {
"references-view": [
{
"id": "references-view.tree",
"name": "Results",
"when": "reference-list.isActive"
}
]
},
"commands": [
{
"command": "references-view.findReferences",
"title": "Find All References",
"category": "References"
},
{
"command": "references-view.findImplementations",
"title": "Find All Implementations",
"category": "References"
},
{
"command": "references-view.clearHistory",
"title": "Clear History",
"category": "References",
"icon": "$(clear-all)"
},
{
"command": "references-view.clear",
"title": "Clear",
"category": "References",
"icon": "$(clear-all)"
},
{
"command": "references-view.refresh",
"title": "Refresh",
"category": "References",
"icon": "$(refresh)"
},
{
"command": "references-view.pickFromHistory",
"title": "Show History",
"category": "References"
},
{
"command": "references-view.removeReferenceItem",
"title": "Dismiss",
"icon": "$(close)"
},
{
"command": "references-view.copy",
"title": "Copy"
},
{
"command": "references-view.copyAll",
"title": "Copy All"
},
{
"command": "references-view.copyPath",
"title": "Copy Path"
},
{
"command": "references-view.refind",
"title": "Rerun",
"icon": "$(refresh)"
},
{
"command": "references-view.showCallHierarchy",
"title": "Show Call Hierarchy",
"category": "Calls"
},
{
"command": "references-view.showOutgoingCalls",
"title": "Show Outgoing Calls",
"category": "Calls",
"icon": "$(call-outgoing)"
},
{
"command": "references-view.showIncomingCalls",
"title": "Show Incoming Calls",
"category": "Calls",
"icon": "$(call-incoming)"
},
{
"command": "references-view.removeCallItem",
"title": "Dismiss",
"icon": "$(close)"
},
{
"command": "references-view.next",
"title": "Go to Next Reference",
"enablement": "references-view.canNavigate"
},
{
"command": "references-view.prev",
"title": "Go to Previous Reference",
"enablement": "references-view.canNavigate"
},
{
"command": "references-view.showTypeHierarchy",
"title": "Show Type Hierarchy",
"category": "Types"
},
{
"command": "references-view.showSupertypes",
"title": "Show Supertypes",
"category": "Types",
"icon": "$(type-hierarchy-super)"
},
{
"command": "references-view.showSubtypes",
"title": "Show Subtypes",
"category": "Types",
"icon": "$(type-hierarchy-sub)"
},
{
"command": "references-view.removeTypeItem",
"title": "Dismiss",
"icon": "$(close)"
}
],
"menus": {
"editor/context": [
{
"command": "references-view.findReferences",
"when": "editorHasReferenceProvider",
"group": "0_navigation@1"
},
{
"command": "references-view.findImplementations",
"when": "editorHasImplementationProvider",
"group": "0_navigation@2"
},
{
"command": "references-view.showCallHierarchy",
"when": "editorHasCallHierarchyProvider",
"group": "0_navigation@3"
},
{
"command": "references-view.showTypeHierarchy",
"when": "editorHasTypeHierarchyProvider",
"group": "0_navigation@4"
}
],
"view/title": [
{
"command": "references-view.clear",
"group": "navigation@3",
"when": "view == references-view.tree && reference-list.hasResult"
},
{
"command": "references-view.clearHistory",
"group": "navigation@3",
"when": "view == references-view.tree && reference-list.hasHistory && !reference-list.hasResult"
},
{
"command": "references-view.refresh",
"group": "navigation@2",
"when": "view == references-view.tree && reference-list.hasResult"
},
{
"command": "references-view.showOutgoingCalls",
"group": "navigation@1",
"when": "view == references-view.tree && reference-list.hasResult && reference-list.source == callHierarchy && references-view.callHierarchyMode == showIncoming"
},
{
"command": "references-view.showIncomingCalls",
"group": "navigation@1",
"when": "view == references-view.tree && reference-list.hasResult && reference-list.source == callHierarchy && references-view.callHierarchyMode == showOutgoing"
},
{
"command": "references-view.showSupertypes",
"group": "navigation@1",
"when": "view == references-view.tree && reference-list.hasResult && reference-list.source == typeHierarchy && references-view.typeHierarchyMode != supertypes"
},
{
"command": "references-view.showSubtypes",
"group": "navigation@1",
"when": "view == references-view.tree && reference-list.hasResult && reference-list.source == typeHierarchy && references-view.typeHierarchyMode != subtypes"
}
],
"view/item/context": [
{
"command": "references-view.removeReferenceItem",
"group": "inline",
"when": "view == references-view.tree && viewItem == file-item || view == references-view.tree && viewItem == reference-item"
},
{
"command": "references-view.removeCallItem",
"group": "inline",
"when": "view == references-view.tree && viewItem == call-item"
},
{
"command": "references-view.removeTypeItem",
"group": "inline",
"when": "view == references-view.tree && viewItem == type-item"
},
{
"command": "references-view.refind",
"group": "inline",
"when": "view == references-view.tree && viewItem == history-item"
},
{
"command": "references-view.removeReferenceItem",
"group": "1",
"when": "view == references-view.tree && viewItem == file-item || view == references-view.tree && viewItem == reference-item"
},
{
"command": "references-view.removeCallItem",
"group": "1",
"when": "view == references-view.tree && viewItem == call-item"
},
{
"command": "references-view.removeTypeItem",
"group": "1",
"when": "view == references-view.tree && viewItem == type-item"
},
{
"command": "references-view.refind",
"group": "1",
"when": "view == references-view.tree && viewItem == history-item"
},
{
"command": "references-view.copy",
"group": "2@1",
"when": "view == references-view.tree && viewItem == file-item || view == references-view.tree && viewItem == reference-item"
},
{
"command": "references-view.copyPath",
"group": "2@2",
"when": "view == references-view.tree && viewItem == file-item"
},
{
"command": "references-view.copyAll",
"group": "2@3",
"when": "view == references-view.tree && viewItem == file-item || view == references-view.tree && viewItem == reference-item"
},
{
"command": "references-view.showOutgoingCalls",
"group": "1",
"when": "view == references-view.tree && viewItem == call-item"
},
{
"command": "references-view.showIncomingCalls",
"group": "1",
"when": "view == references-view.tree && viewItem == call-item"
},
{
"command": "references-view.showSupertypes",
"group": "1",
"when": "view == references-view.tree && viewItem == type-item"
},
{
"command": "references-view.showSubtypes",
"group": "1",
"when": "view == references-view.tree && viewItem == type-item"
}
],
"commandPalette": [
{
"command": "references-view.removeReferenceItem",
"when": "never"
},
{
"command": "references-view.removeCallItem",
"when": "never"
},
{
"command": "references-view.removeTypeItem",
"when": "never"
},
{
"command": "references-view.copy",
"when": "never"
},
{
"command": "references-view.copyAll",
"when": "never"
},
{
"command": "references-view.copyPath",
"when": "never"
},
{
"command": "references-view.refind",
"when": "never"
},
{
"command": "references-view.findReferences",
"when": "editorHasReferenceProvider"
},
{
"command": "references-view.clear",
"when": "reference-list.hasResult"
},
{
"command": "references-view.clearHistory",
"when": "reference-list.isActive && !reference-list.hasResult"
},
{
"command": "references-view.refresh",
"when": "reference-list.hasResult"
},
{
"command": "references-view.pickFromHistory",
"when": "reference-list.isActive"
},
{
"command": "references-view.next",
"when": "never"
},
{
"command": "references-view.prev",
"when": "never"
}
]
},
"keybindings": [
{
"command": "references-view.findReferences",
"when": "editorHasReferenceProvider",
"key": "shift+alt+f12"
},
{
"command": "references-view.next",
"when": "reference-list.hasResult",
"key": "f4"
},
{
"command": "references-view.prev",
"when": "reference-list.hasResult",
"key": "shift+f4"
},
{
"command": "references-view.showCallHierarchy",
"when": "editorHasCallHierarchyProvider",
"key": "shift+alt+h"
}
]
},
"scripts": {
"compile": "npx gulp compile-extension:references-view",
"watch": "npx gulp watch-extension:references-view"
},
"devDependencies": {
"@types/node": "16.x"
}
}

View file

@ -0,0 +1,78 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { SymbolsTree } from '../tree';
import { ContextKey } from '../utils';
import { CallItem, CallsDirection, CallsTreeInput } from './model';
export function register(tree: SymbolsTree, context: vscode.ExtensionContext): void {
const direction = new RichCallsDirection(context.workspaceState, CallsDirection.Incoming);
function showCallHierarchy() {
if (vscode.window.activeTextEditor) {
const input = new CallsTreeInput(new vscode.Location(vscode.window.activeTextEditor.document.uri, vscode.window.activeTextEditor.selection.active), direction.value);
tree.setInput(input);
}
}
function setCallsDirection(value: CallsDirection, anchor: CallItem | unknown) {
direction.value = value;
let newInput: CallsTreeInput | undefined;
const oldInput = tree.getInput();
if (anchor instanceof CallItem) {
newInput = new CallsTreeInput(new vscode.Location(anchor.item.uri, anchor.item.selectionRange.start), direction.value);
} else if (oldInput instanceof CallsTreeInput) {
newInput = new CallsTreeInput(oldInput.location, direction.value);
}
if (newInput) {
tree.setInput(newInput);
}
}
context.subscriptions.push(
vscode.commands.registerCommand('references-view.showCallHierarchy', showCallHierarchy),
vscode.commands.registerCommand('references-view.showOutgoingCalls', (item: CallItem | unknown) => setCallsDirection(CallsDirection.Outgoing, item)),
vscode.commands.registerCommand('references-view.showIncomingCalls', (item: CallItem | unknown) => setCallsDirection(CallsDirection.Incoming, item)),
vscode.commands.registerCommand('references-view.removeCallItem', removeCallItem)
);
}
function removeCallItem(item: CallItem | unknown): void {
if (item instanceof CallItem) {
item.remove();
}
}
class RichCallsDirection {
private static _key = 'references-view.callHierarchyMode';
private _ctxMode = new ContextKey<'showIncoming' | 'showOutgoing'>('references-view.callHierarchyMode');
constructor(
private _mem: vscode.Memento,
private _value: CallsDirection = CallsDirection.Outgoing,
) {
const raw = _mem.get<number>(RichCallsDirection._key);
if (typeof raw === 'number' && raw >= 0 && raw <= 1) {
this.value = raw;
} else {
this.value = _value;
}
}
get value() {
return this._value;
}
set value(value: CallsDirection) {
this._value = value;
this._ctxMode.set(this._value === CallsDirection.Incoming ? 'showIncoming' : 'showOutgoing');
this._mem.update(RichCallsDirection._key, value);
}
}

View file

@ -0,0 +1,205 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { SymbolItemDragAndDrop, SymbolItemEditorHighlights, SymbolItemNavigation, SymbolTreeInput } from '../references-view';
import { asResourceUrl, del, getThemeIcon, tail } from '../utils';
export class CallsTreeInput implements SymbolTreeInput<CallItem> {
readonly title: string;
readonly contextValue: string = 'callHierarchy';
constructor(
readonly location: vscode.Location,
readonly direction: CallsDirection,
) {
this.title = direction === CallsDirection.Incoming
? 'Callers Of'
: 'Calls From';
}
async resolve() {
const items = await Promise.resolve(vscode.commands.executeCommand<vscode.CallHierarchyItem[]>('vscode.prepareCallHierarchy', this.location.uri, this.location.range.start));
const model = new CallsModel(this.direction, items ?? []);
const provider = new CallItemDataProvider(model);
if (model.roots.length === 0) {
return;
}
return {
provider,
get message() { return model.roots.length === 0 ? 'No results.' : undefined; },
navigation: model,
highlights: model,
dnd: model,
dispose() {
provider.dispose();
}
};
}
with(location: vscode.Location): CallsTreeInput {
return new CallsTreeInput(location, this.direction);
}
}
export const enum CallsDirection {
Incoming,
Outgoing
}
export class CallItem {
children?: CallItem[];
constructor(
readonly model: CallsModel,
readonly item: vscode.CallHierarchyItem,
readonly parent: CallItem | undefined,
readonly locations: vscode.Location[] | undefined
) { }
remove(): void {
this.model.remove(this);
}
}
class CallsModel implements SymbolItemNavigation<CallItem>, SymbolItemEditorHighlights<CallItem>, SymbolItemDragAndDrop<CallItem> {
readonly roots: CallItem[] = [];
private readonly _onDidChange = new vscode.EventEmitter<CallsModel>();
readonly onDidChange = this._onDidChange.event;
constructor(readonly direction: CallsDirection, items: vscode.CallHierarchyItem[]) {
this.roots = items.map(item => new CallItem(this, item, undefined, undefined));
}
private async _resolveCalls(call: CallItem): Promise<CallItem[]> {
if (this.direction === CallsDirection.Incoming) {
const calls = await vscode.commands.executeCommand<vscode.CallHierarchyIncomingCall[]>('vscode.provideIncomingCalls', call.item);
return calls ? calls.map(item => new CallItem(this, item.from, call, item.fromRanges.map(range => new vscode.Location(item.from.uri, range)))) : [];
} else {
const calls = await vscode.commands.executeCommand<vscode.CallHierarchyOutgoingCall[]>('vscode.provideOutgoingCalls', call.item);
return calls ? calls.map(item => new CallItem(this, item.to, call, item.fromRanges.map(range => new vscode.Location(call.item.uri, range)))) : [];
}
}
async getCallChildren(call: CallItem): Promise<CallItem[]> {
if (!call.children) {
call.children = await this._resolveCalls(call);
}
return call.children;
}
// -- navigation
location(item: CallItem) {
return new vscode.Location(item.item.uri, item.item.range);
}
nearest(uri: vscode.Uri, _position: vscode.Position): CallItem | undefined {
return this.roots.find(item => item.item.uri.toString() === uri.toString()) ?? this.roots[0];
}
next(from: CallItem): CallItem {
return this._move(from, true) ?? from;
}
previous(from: CallItem): CallItem {
return this._move(from, false) ?? from;
}
private _move(item: CallItem, fwd: boolean): CallItem | void {
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];
}
}
// --- dnd
getDragUri(item: CallItem): vscode.Uri | undefined {
return asResourceUrl(item.item.uri, item.item.range);
}
// --- highlights
getEditorHighlights(item: CallItem, uri: vscode.Uri): vscode.Range[] | undefined {
if (!item.locations) {
return item.item.uri.toString() === uri.toString() ? [item.item.selectionRange] : undefined;
}
return item.locations
.filter(loc => loc.uri.toString() === uri.toString())
.map(loc => loc.range);
}
remove(item: CallItem) {
const isInRoot = this.roots.includes(item);
const siblings = isInRoot ? this.roots : item.parent?.children;
if (siblings) {
del(siblings, item);
this._onDidChange.fire(this);
}
}
}
class CallItemDataProvider implements vscode.TreeDataProvider<CallItem> {
private readonly _emitter = new vscode.EventEmitter<CallItem | undefined>();
readonly onDidChangeTreeData = this._emitter.event;
private readonly _modelListener: vscode.Disposable;
constructor(private _model: CallsModel) {
this._modelListener = _model.onDidChange(e => this._emitter.fire(e instanceof CallItem ? e : undefined));
}
dispose(): void {
this._emitter.dispose();
this._modelListener.dispose();
}
getTreeItem(element: CallItem): vscode.TreeItem {
const item = new vscode.TreeItem(element.item.name);
item.description = element.item.detail;
item.tooltip = item.label ? `${item.label} - ${element.item.detail}` : element.item.detail;
item.contextValue = 'call-item';
item.iconPath = getThemeIcon(element.item.kind);
item.command = {
command: 'vscode.open',
title: 'Open Call',
arguments: [
element.item.uri,
<vscode.TextDocumentShowOptions>{ selection: element.item.selectionRange.with({ end: element.item.selectionRange.start }) }
]
};
item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
return item;
}
getChildren(element?: CallItem | undefined) {
return element
? this._model.getCallChildren(element)
: this._model.roots;
}
getParent(element: CallItem) {
return element.parent;
}
}

View file

@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as calls from './calls';
import * as references from './references';
import { SymbolTree, SymbolTreeInput } from './references-view';
import { SymbolsTree } from './tree';
import * as types from './types';
export function activate(context: vscode.ExtensionContext): SymbolTree {
const tree = new SymbolsTree();
references.register(tree, context);
calls.register(tree, context);
types.register(tree, context);
function setInput(input: SymbolTreeInput<unknown>) {
tree.setInput(input);
}
return { setInput };
}

View file

@ -0,0 +1,66 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { SymbolItemEditorHighlights } from './references-view';
export class EditorHighlights<T> {
private readonly _decorationType = vscode.window.createTextEditorDecorationType({
backgroundColor: new vscode.ThemeColor('editor.findMatchHighlightBackground'),
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
overviewRulerLane: vscode.OverviewRulerLane.Center,
overviewRulerColor: new vscode.ThemeColor('editor.findMatchHighlightBackground'),
});
private readonly disposables: vscode.Disposable[] = [];
private readonly _ignore = new Set<string>();
constructor(private readonly _view: vscode.TreeView<T>, private readonly _delegate: SymbolItemEditorHighlights<T>) {
this.disposables.push(
vscode.workspace.onDidChangeTextDocument(e => this._ignore.add(e.document.uri.toString())),
vscode.window.onDidChangeActiveTextEditor(() => _view.visible && this.update()),
_view.onDidChangeVisibility(e => e.visible ? this._show() : this._hide()),
_view.onDidChangeSelection(() => _view.visible && this.update())
);
this._show();
}
dispose() {
vscode.Disposable.from(...this.disposables).dispose();
for (const editor of vscode.window.visibleTextEditors) {
editor.setDecorations(this._decorationType, []);
}
}
private _show(): void {
const { activeTextEditor: editor } = vscode.window;
if (!editor || !editor.viewColumn) {
return;
}
if (this._ignore.has(editor.document.uri.toString())) {
return;
}
const [anchor] = this._view.selection;
if (!anchor) {
return;
}
const ranges = this._delegate.getEditorHighlights(anchor, editor.document.uri);
if (ranges) {
editor.setDecorations(this._decorationType, ranges);
}
}
private _hide(): void {
for (const editor of vscode.window.visibleTextEditors) {
editor.setDecorations(this._decorationType, []);
}
}
update(): void {
this._hide();
this._show();
}
}

View file

@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { SymbolItemNavigation } from './references-view';
import { ContextKey } from './utils';
export class Navigation {
private readonly _disposables: vscode.Disposable[] = [];
private readonly _ctxCanNavigate = new ContextKey<boolean>('references-view.canNavigate');
private _delegate?: SymbolItemNavigation<unknown>;
constructor(private readonly _view: vscode.TreeView<unknown>) {
this._disposables.push(
vscode.commands.registerCommand('references-view.next', () => this.next(false)),
vscode.commands.registerCommand('references-view.prev', () => this.previous(false)),
);
}
dispose(): void {
vscode.Disposable.from(...this._disposables).dispose();
}
update(delegate: SymbolItemNavigation<unknown> | undefined) {
this._delegate = delegate;
this._ctxCanNavigate.set(Boolean(this._delegate));
}
private _anchor(): undefined | unknown {
if (!this._delegate) {
return undefined;
}
const [sel] = this._view.selection;
if (sel) {
return sel;
}
if (!vscode.window.activeTextEditor) {
return undefined;
}
return this._delegate.nearest(vscode.window.activeTextEditor.document.uri, vscode.window.activeTextEditor.selection.active);
}
private _open(loc: vscode.Location, preserveFocus: boolean) {
vscode.commands.executeCommand('vscode.open', loc.uri, {
selection: new vscode.Selection(loc.range.start, loc.range.start),
preserveFocus
});
}
previous(preserveFocus: boolean): void {
if (!this._delegate) {
return;
}
const item = this._anchor();
if (!item) {
return;
}
const newItem = this._delegate.previous(item);
const newLocation = this._delegate.location(newItem);
if (newLocation) {
this._view.reveal(newItem, { select: true, focus: true });
this._open(newLocation, preserveFocus);
}
}
next(preserveFocus: boolean): void {
if (!this._delegate) {
return;
}
const item = this._anchor();
if (!item) {
return;
}
const newItem = this._delegate.next(item);
const newLocation = this._delegate.location(newItem);
if (newLocation) {
this._view.reveal(newItem, { select: true, focus: true });
this._open(newLocation, preserveFocus);
}
}
}

View file

@ -0,0 +1,149 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
/**
* This interface describes the shape for the references viewlet API. It consists
* of a single `setInput` function which must be called with a full implementation
* of the `SymbolTreeInput`-interface. To acquire this API use the default mechanics, e.g:
*
* ```ts
* // get references viewlet API
* const api = await vscode.extensions.getExtension<SymbolTree>('ms-vscode.references-view').activate();
*
* // instantiate and set input which updates the view
* const myInput: SymbolTreeInput<MyItems> = ...
* api.setInput(myInput)
* ```
*/
export interface SymbolTree {
/**
* Set the contents of the references viewlet.
*
* @param input A symbol tree input object
*/
setInput(input: SymbolTreeInput<unknown>): void;
}
/**
* A symbol tree input is the entry point for populating the references viewlet.
* Inputs must be anchored at a code location, they must have a title, and they
* must resolve to a model.
*/
export interface SymbolTreeInput<T> {
/**
* The value of the `reference-list.source` context key. Use this to control
* input dependent commands.
*/
readonly contextValue: string;
/**
* The (short) title of this input, like "Implementations" or "Callers Of"
*/
readonly title: string;
/**
* The location at which this position is anchored. Locations are validated and inputs
* with "funny" locations might be ignored
*/
readonly location: vscode.Location;
/**
* Resolve this input to a model that contains the actual data. When there are no result
* than `undefined` or `null` should be returned.
*/
resolve(): vscode.ProviderResult<SymbolTreeModel<T>>;
/**
* This function is called when re-running from history. The symbols tree has tracked
* the original location of this input and that is now passed to this input. The
* implementation of this function should return a clone where the `location`-property
* uses the provided `location`
*
* @param location The location at which the new input should be anchored.
* @returns A new input which location is anchored at the position.
*/
with(location: vscode.Location): SymbolTreeInput<T>;
}
/**
* A symbol tree model which is used to populate the symbols tree.
*/
export interface SymbolTreeModel<T> {
/**
* A tree data provider which is used to populate the symbols tree.
*/
provider: vscode.TreeDataProvider<T>;
/**
* An optional message that is displayed above the tree. Whenever the provider
* fires a change event this message is read again.
*/
message: string | undefined;
/**
* Optional support for symbol navigation. When implemented, navigation commands like
* "Go to Next" and "Go to Previous" will be working with this model.
*/
navigation?: SymbolItemNavigation<T>;
/**
* Optional support for editor highlights. WHen implemented, the editor will highlight
* symbol ranges in the source code.
*/
highlights?: SymbolItemEditorHighlights<T>;
/**
* Optional support for drag and drop.
*/
dnd?: SymbolItemDragAndDrop<T>;
/**
* Optional dispose function which is invoked when this model is
* needed anymore
*/
dispose?(): void;
}
/**
* Interface to support the built-in symbol navigation.
*/
export interface SymbolItemNavigation<T> {
/**
* Return the item that is the nearest to the given location or `undefined`
*/
nearest(uri: vscode.Uri, position: vscode.Position): T | undefined;
/**
* Return the next item from the given item or the item itself.
*/
next(from: T): T;
/**
* Return the previous item from the given item or the item itself.
*/
previous(from: T): T;
/**
* Return the location of the given item.
*/
location(item: T): vscode.Location | undefined;
}
/**
* Interface to support the built-in editor highlights.
*/
export interface SymbolItemEditorHighlights<T> {
/**
* Given an item and an uri return an array of ranges to highlight.
*/
getEditorHighlights(item: T, uri: vscode.Uri): vscode.Range[] | undefined;
}
export interface SymbolItemDragAndDrop<T> {
getDragUri(item: T): vscode.Uri | undefined;
}

View file

@ -0,0 +1,95 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { SymbolsTree } from '../tree';
import { FileItem, ReferenceItem, ReferencesModel, ReferencesTreeInput } from './model';
export function register(tree: SymbolsTree, context: vscode.ExtensionContext): void {
function findLocations(title: string, command: string) {
if (vscode.window.activeTextEditor) {
const input = new ReferencesTreeInput(title, new vscode.Location(vscode.window.activeTextEditor.document.uri, vscode.window.activeTextEditor.selection.active), command);
tree.setInput(input);
}
}
context.subscriptions.push(
vscode.commands.registerCommand('references-view.findReferences', () => findLocations('References', 'vscode.executeReferenceProvider')),
vscode.commands.registerCommand('references-view.findImplementations', () => findLocations('Implementations', 'vscode.executeImplementationProvider')),
// --- legacy name
vscode.commands.registerCommand('references-view.find', (...args: any[]) => vscode.commands.executeCommand('references-view.findReferences', ...args)),
vscode.commands.registerCommand('references-view.removeReferenceItem', removeReferenceItem),
vscode.commands.registerCommand('references-view.copy', copyCommand),
vscode.commands.registerCommand('references-view.copyAll', copyAllCommand),
vscode.commands.registerCommand('references-view.copyPath', copyPathCommand),
);
// --- references.preferredLocation setting
let showReferencesDisposable: vscode.Disposable | undefined;
const config = 'references.preferredLocation';
function updateShowReferences(event?: vscode.ConfigurationChangeEvent) {
if (event && !event.affectsConfiguration(config)) {
return;
}
const value = vscode.workspace.getConfiguration().get<string>(config);
showReferencesDisposable?.dispose();
showReferencesDisposable = undefined;
if (value === 'view') {
showReferencesDisposable = vscode.commands.registerCommand('editor.action.showReferences', async (uri: vscode.Uri, position: vscode.Position, locations: vscode.Location[]) => {
const input = new ReferencesTreeInput('References', new vscode.Location(uri, position), 'vscode.executeReferenceProvider', locations);
tree.setInput(input);
});
}
}
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(updateShowReferences));
context.subscriptions.push({ dispose: () => showReferencesDisposable?.dispose() });
updateShowReferences();
}
const copyAllCommand = async (item: ReferenceItem | FileItem | unknown) => {
if (item instanceof ReferenceItem) {
copyCommand(item.file.model);
} else if (item instanceof FileItem) {
copyCommand(item.model);
}
};
function removeReferenceItem(item: FileItem | ReferenceItem | unknown) {
if (item instanceof FileItem) {
item.remove();
} else if (item instanceof ReferenceItem) {
item.remove();
}
}
async function copyCommand(item: ReferencesModel | ReferenceItem | FileItem | unknown) {
let val: string | undefined;
if (item instanceof ReferencesModel) {
val = await item.asCopyText();
} else if (item instanceof ReferenceItem) {
val = await item.asCopyText();
} else if (item instanceof FileItem) {
val = await item.asCopyText();
}
if (val) {
await vscode.env.clipboard.writeText(val);
}
}
async function copyPathCommand(item: FileItem | unknown) {
if (item instanceof FileItem) {
if (item.uri.scheme === 'file') {
vscode.env.clipboard.writeText(item.uri.fsPath);
} else {
vscode.env.clipboard.writeText(item.uri.toString(true));
}
}
}

View file

@ -0,0 +1,384 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { SymbolItemDragAndDrop, SymbolItemEditorHighlights, SymbolItemNavigation, SymbolTreeInput, SymbolTreeModel } from '../references-view';
import { asResourceUrl, del, getPreviewChunks, tail } from '../utils';
export class ReferencesTreeInput implements SymbolTreeInput<FileItem | ReferenceItem> {
readonly contextValue: string;
constructor(
readonly title: string,
readonly location: vscode.Location,
private readonly _command: string,
private readonly _result?: vscode.Location[] | vscode.LocationLink[]
) {
this.contextValue = _command;
}
async resolve() {
let model: ReferencesModel;
if (this._result) {
model = new ReferencesModel(this._result);
} else {
const resut = await Promise.resolve(vscode.commands.executeCommand<vscode.Location[] | vscode.LocationLink[]>(this._command, this.location.uri, this.location.range.start));
model = new ReferencesModel(resut ?? []);
}
if (model.items.length === 0) {
return;
}
const provider = new ReferencesTreeDataProvider(model);
return <SymbolTreeModel<FileItem | ReferenceItem>>{
provider,
get message() { return model.message; },
navigation: model,
highlights: model,
dnd: model,
dispose(): void {
provider.dispose();
}
};
}
with(location: vscode.Location): ReferencesTreeInput {
return new ReferencesTreeInput(this.title, location, this._command);
}
}
export class ReferencesModel implements SymbolItemNavigation<FileItem | ReferenceItem>, SymbolItemEditorHighlights<FileItem | ReferenceItem>, SymbolItemDragAndDrop<FileItem | ReferenceItem> {
private _onDidChange = new vscode.EventEmitter<FileItem | ReferenceItem | undefined>();
readonly onDidChangeTreeData = this._onDidChange.event;
readonly items: FileItem[] = [];
constructor(locations: vscode.Location[] | vscode.LocationLink[]) {
let last: FileItem | undefined;
for (const item of locations.sort(ReferencesModel._compareLocations)) {
const loc = item instanceof vscode.Location
? item
: new vscode.Location(item.targetUri, item.targetRange);
if (!last || ReferencesModel._compareUriIgnoreFragment(last.uri, loc.uri) !== 0) {
last = new FileItem(loc.uri.with({ fragment: '' }), [], this);
this.items.push(last);
}
last.references.push(new ReferenceItem(loc, last));
}
}
private static _compareUriIgnoreFragment(a: vscode.Uri, b: vscode.Uri): number {
let aStr = a.with({ fragment: '' }).toString();
let bStr = b.with({ fragment: '' }).toString();
if (aStr < bStr) {
return -1;
} else if (aStr > bStr) {
return 1;
}
return 0;
}
private static _compareLocations(a: vscode.Location | vscode.LocationLink, b: vscode.Location | vscode.LocationLink): number {
let aUri = a instanceof vscode.Location ? a.uri : a.targetUri;
let bUri = b instanceof vscode.Location ? b.uri : b.targetUri;
if (aUri.toString() < bUri.toString()) {
return -1;
} else if (aUri.toString() > bUri.toString()) {
return 1;
}
let aRange = a instanceof vscode.Location ? a.range : a.targetRange;
let bRange = b instanceof vscode.Location ? b.range : b.targetRange;
if (aRange.start.isBefore(bRange.start)) {
return -1;
} else if (aRange.start.isAfter(bRange.start)) {
return 1;
} else {
return 0;
}
}
// --- adapter
get message() {
if (this.items.length === 0) {
return 'No results.';
}
const total = this.items.reduce((prev, cur) => prev + cur.references.length, 0);
const files = this.items.length;
if (total === 1 && files === 1) {
return `${total} result in ${files} file`;
} else if (total === 1) {
return `${total} result in ${files} files`;
} else if (files === 1) {
return `${total} results in ${files} file`;
} else {
return `${total} results in ${files} files`;
}
}
location(item: FileItem | ReferenceItem) {
return item instanceof ReferenceItem ? item.location : undefined;
}
nearest(uri: vscode.Uri, position: vscode.Position): FileItem | ReferenceItem | undefined {
if (this.items.length === 0) {
return;
}
// NOTE: this.items is sorted by location (uri/range)
for (const item of this.items) {
if (item.uri.toString() === uri.toString()) {
// (1) pick the item at the request position
for (const ref of item.references) {
if (ref.location.range.contains(position)) {
return ref;
}
}
// (2) pick the first item after or last before the request position
let lastBefore: ReferenceItem | undefined;
for (const ref of item.references) {
if (ref.location.range.end.isAfter(position)) {
return ref;
}
lastBefore = ref;
}
if (lastBefore) {
return lastBefore;
}
break;
}
}
// (3) pick the file with the longest common prefix
let best = 0;
let bestValue = ReferencesModel._prefixLen(this.items[best].toString(), uri.toString());
for (let i = 1; i < this.items.length; i++) {
let value = ReferencesModel._prefixLen(this.items[i].uri.toString(), uri.toString());
if (value > bestValue) {
best = i;
}
}
return this.items[best].references[0];
}
private static _prefixLen(a: string, b: string): number {
let pos = 0;
while (pos < a.length && pos < b.length && a.charCodeAt(pos) === b.charCodeAt(pos)) {
pos += 1;
}
return pos;
}
next(item: FileItem | ReferenceItem): FileItem | ReferenceItem {
return this._move(item, true) ?? item;
}
previous(item: FileItem | ReferenceItem): FileItem | ReferenceItem {
return this._move(item, false) ?? item;
}
private _move(item: FileItem | ReferenceItem, fwd: boolean): ReferenceItem | void {
const delta = fwd ? +1 : -1;
const _move = (item: FileItem): FileItem => {
const idx = (this.items.indexOf(item) + delta + this.items.length) % this.items.length;
return this.items[idx];
};
if (item instanceof FileItem) {
if (fwd) {
return _move(item).references[0];
} else {
return tail(_move(item).references);
}
}
if (item instanceof ReferenceItem) {
const idx = item.file.references.indexOf(item) + delta;
if (idx < 0) {
return tail(_move(item.file).references);
} else if (idx >= item.file.references.length) {
return _move(item.file).references[0];
} else {
return item.file.references[idx];
}
}
}
getEditorHighlights(_item: FileItem | ReferenceItem, uri: vscode.Uri): vscode.Range[] | undefined {
const file = this.items.find(file => file.uri.toString() === uri.toString());
return file?.references.map(ref => ref.location.range);
}
remove(item: FileItem | ReferenceItem) {
if (item instanceof FileItem) {
del(this.items, item);
this._onDidChange.fire(undefined);
} else {
del(item.file.references, item);
if (item.file.references.length === 0) {
del(this.items, item.file);
this._onDidChange.fire(undefined);
} else {
this._onDidChange.fire(item.file);
}
}
}
async asCopyText() {
let result = '';
for (const item of this.items) {
result += `${await item.asCopyText()}\n`;
}
return result;
}
getDragUri(item: FileItem | ReferenceItem): vscode.Uri | undefined {
if (item instanceof FileItem) {
return item.uri;
} else {
return asResourceUrl(item.file.uri, item.location.range);
}
}
}
class ReferencesTreeDataProvider implements vscode.TreeDataProvider<FileItem | ReferenceItem>{
private readonly _listener: vscode.Disposable;
private readonly _onDidChange = new vscode.EventEmitter<FileItem | ReferenceItem | undefined>();
readonly onDidChangeTreeData = this._onDidChange.event;
constructor(private readonly _model: ReferencesModel) {
this._listener = _model.onDidChangeTreeData(() => this._onDidChange.fire(undefined));
}
dispose(): void {
this._onDidChange.dispose();
this._listener.dispose();
}
async getTreeItem(element: FileItem | ReferenceItem) {
if (element instanceof FileItem) {
// files
const result = new vscode.TreeItem(element.uri);
result.contextValue = 'file-item';
result.description = true;
result.iconPath = vscode.ThemeIcon.File;
result.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
return result;
} else {
// references
const { range } = element.location;
const doc = await element.getDocument(true);
const { before, inside, after } = getPreviewChunks(doc, range);
const label: vscode.TreeItemLabel = {
label: before + inside + after,
highlights: [[before.length, before.length + inside.length]]
};
const result = new vscode.TreeItem(label);
result.collapsibleState = vscode.TreeItemCollapsibleState.None;
result.contextValue = 'reference-item';
result.command = {
command: 'vscode.open',
title: 'Open Reference',
arguments: [
element.location.uri,
<vscode.TextDocumentShowOptions>{ selection: range.with({ end: range.start }) }
]
};
return result;
}
}
async getChildren(element?: FileItem | ReferenceItem) {
if (!element) {
return this._model.items;
}
if (element instanceof FileItem) {
return element.references;
}
return undefined;
}
getParent(element: FileItem | ReferenceItem) {
return element instanceof ReferenceItem ? element.file : undefined;
}
}
export class FileItem {
constructor(
readonly uri: vscode.Uri,
readonly references: Array<ReferenceItem>,
readonly model: ReferencesModel
) { }
// --- adapter
remove(): void {
this.model.remove(this);
}
async asCopyText() {
let result = `${vscode.workspace.asRelativePath(this.uri)}\n`;
for (let ref of this.references) {
result += ` ${await ref.asCopyText()}\n`;
}
return result;
}
}
export class ReferenceItem {
private _document: Thenable<vscode.TextDocument> | undefined;
constructor(
readonly location: vscode.Location,
readonly file: FileItem,
) { }
async getDocument(warmUpNext?: boolean) {
if (!this._document) {
this._document = vscode.workspace.openTextDocument(this.location.uri);
}
if (warmUpNext) {
// load next document once this document has been loaded
const next = this.file.model.next(this.file);
if (next instanceof FileItem && next !== this.file) {
vscode.workspace.openTextDocument(next.uri);
} else if (next instanceof ReferenceItem) {
vscode.workspace.openTextDocument(next.location.uri);
}
}
return this._document;
}
// --- adapter
remove(): void {
this.file.model.remove(this);
}
async asCopyText() {
let doc = await this.getDocument();
let chunks = getPreviewChunks(doc, this.location.range, 21, false);
return `${this.location.range.start.line + 1}, ${this.location.range.start.character + 1}: ${chunks.before + chunks.inside + chunks.after}`;
}
}

View file

@ -0,0 +1,349 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { EditorHighlights } from './highlights';
import { Navigation } from './navigation';
import { SymbolItemDragAndDrop, SymbolTreeInput } from './references-view';
import { ContextKey, isValidRequestPosition, WordAnchor } from './utils';
export class SymbolsTree {
readonly viewId = 'references-view.tree';
private readonly _ctxIsActive = new ContextKey<boolean>('reference-list.isActive');
private readonly _ctxHasResult = new ContextKey<boolean>('reference-list.hasResult');
private readonly _ctxInputSource = new ContextKey<string>('reference-list.source');
private readonly _history = new TreeInputHistory(this);
private readonly _provider = new TreeDataProviderDelegate();
private readonly _dnd = new TreeDndDelegate();
private readonly _tree: vscode.TreeView<unknown>;
private readonly _navigation: Navigation;
private _input?: SymbolTreeInput<unknown>;
private _sessionDisposable?: vscode.Disposable;
constructor() {
this._tree = vscode.window.createTreeView<unknown>(this.viewId, {
treeDataProvider: this._provider,
showCollapseAll: true,
dragAndDropController: this._dnd
});
this._navigation = new Navigation(this._tree);
}
dispose(): void {
this._history.dispose();
this._tree.dispose();
this._sessionDisposable?.dispose();
}
getInput(): SymbolTreeInput<unknown> | undefined {
return this._input;
}
async setInput(input: SymbolTreeInput<unknown>) {
if (!await isValidRequestPosition(input.location.uri, input.location.range.start)) {
this.clearInput();
return;
}
this._ctxInputSource.set(input.contextValue);
this._ctxIsActive.set(true);
this._ctxHasResult.set(true);
vscode.commands.executeCommand(`${this.viewId}.focus`);
const newInputKind = !this._input || Object.getPrototypeOf(this._input) !== Object.getPrototypeOf(input);
this._input = input;
this._sessionDisposable?.dispose();
this._tree.title = input.title;
this._tree.message = newInputKind ? undefined : this._tree.message;
const modelPromise = Promise.resolve(input.resolve());
// set promise to tree data provider to trigger tree loading UI
this._provider.update(modelPromise.then(model => model?.provider ?? this._history));
this._dnd.update(modelPromise.then(model => model?.dnd));
const model = await modelPromise;
if (this._input !== input) {
return;
}
if (!model) {
this.clearInput();
return;
}
this._history.add(input);
this._tree.message = model.message;
// navigation
this._navigation.update(model.navigation);
// reveal & select
const selection = model.navigation?.nearest(input.location.uri, input.location.range.start);
if (selection && this._tree.visible) {
await this._tree.reveal(selection, { select: true, focus: true, expand: true });
}
const disposables: vscode.Disposable[] = [];
// editor highlights
let highlights: EditorHighlights<unknown> | undefined;
if (model.highlights) {
highlights = new EditorHighlights(this._tree, model.highlights);
disposables.push(highlights);
}
// listener
if (model.provider.onDidChangeTreeData) {
disposables.push(model.provider.onDidChangeTreeData(() => {
this._tree.title = input.title;
this._tree.message = model.message;
highlights?.update();
}));
}
if (typeof model.dispose === 'function') {
disposables.push(new vscode.Disposable(() => model.dispose!()));
}
this._sessionDisposable = vscode.Disposable.from(...disposables);
}
clearInput(): void {
this._sessionDisposable?.dispose();
this._input = undefined;
this._ctxHasResult.set(false);
this._ctxInputSource.reset();
this._tree.title = 'References';
this._tree.message = this._history.size === 0 ? 'No results.' : 'No results. Try running a previous search again:';
this._provider.update(Promise.resolve(this._history));
}
}
// --- tree data
interface ActiveTreeDataProviderWrapper {
provider: Promise<vscode.TreeDataProvider<any>>;
}
class TreeDataProviderDelegate implements vscode.TreeDataProvider<undefined> {
provider?: Promise<vscode.TreeDataProvider<any>>;
private _sessionDispoables?: vscode.Disposable;
private _onDidChange = new vscode.EventEmitter<any>();
readonly onDidChangeTreeData = this._onDidChange.event;
update(provider: Promise<vscode.TreeDataProvider<any>>) {
this._sessionDispoables?.dispose();
this._sessionDispoables = undefined;
this._onDidChange.fire(undefined);
this.provider = provider;
provider.then(value => {
if (this.provider === provider && value.onDidChangeTreeData) {
this._sessionDispoables = value.onDidChangeTreeData(this._onDidChange.fire, this._onDidChange);
}
}).catch(err => {
this.provider = undefined;
console.error(err);
});
}
async getTreeItem(element: unknown) {
this._assertProvider();
return (await this.provider).getTreeItem(element);
}
async getChildren(parent?: unknown | undefined) {
this._assertProvider();
return (await this.provider).getChildren(parent);
}
async getParent(element: unknown) {
this._assertProvider();
const provider = await this.provider;
return provider.getParent ? provider.getParent(element) : undefined;
}
private _assertProvider(): asserts this is ActiveTreeDataProviderWrapper {
if (!this.provider) {
throw new Error('MISSING provider');
}
}
}
// --- tree dnd
class TreeDndDelegate implements vscode.TreeDragAndDropController<undefined> {
private _delegate: SymbolItemDragAndDrop<undefined> | undefined;
readonly dropMimeTypes: string[] = [];
readonly dragMimeTypes: string[] = ['text/uri-list'];
update(delegate: Promise<SymbolItemDragAndDrop<unknown> | undefined>) {
this._delegate = undefined;
delegate.then(value => this._delegate = value);
}
handleDrag(source: undefined[], data: vscode.DataTransfer) {
if (this._delegate) {
const urls: string[] = [];
for (let item of source) {
const uri = this._delegate.getDragUri(item);
if (uri) {
urls.push(uri.toString());
}
}
if (urls.length > 0) {
data.set('text/uri-list', new vscode.DataTransferItem(urls.join('\n')));
}
}
}
handleDrop(): void | Thenable<void> {
throw new Error('Method not implemented.');
}
}
// --- history
class HistoryItem {
readonly description: string;
constructor(
readonly key: string,
readonly word: string,
readonly anchor: WordAnchor,
readonly input: SymbolTreeInput<unknown>,
) {
this.description = `${vscode.workspace.asRelativePath(input.location.uri)}${input.title.toLocaleLowerCase()}`;
}
}
class TreeInputHistory implements vscode.TreeDataProvider<HistoryItem>{
private readonly _onDidChangeTreeData = new vscode.EventEmitter<HistoryItem | undefined>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
private readonly _disposables: vscode.Disposable[] = [];
private readonly _ctxHasHistory = new ContextKey<boolean>('reference-list.hasHistory');
private readonly _inputs = new Map<string, HistoryItem>();
constructor(private readonly _tree: SymbolsTree) {
this._disposables.push(
vscode.commands.registerCommand('references-view.clear', () => _tree.clearInput()),
vscode.commands.registerCommand('references-view.clearHistory', () => {
this.clear();
_tree.clearInput();
}),
vscode.commands.registerCommand('references-view.refind', (item) => {
if (item instanceof HistoryItem) {
this._reRunHistoryItem(item);
}
}),
vscode.commands.registerCommand('references-view.refresh', () => {
const item = Array.from(this._inputs.values()).pop();
if (item) {
this._reRunHistoryItem(item);
}
}),
vscode.commands.registerCommand('_references-view.showHistoryItem', async (item) => {
if (item instanceof HistoryItem) {
const position = item.anchor.guessedTrackedPosition() ?? item.input.location.range.start;
return vscode.commands.executeCommand('vscode.open', item.input.location.uri, { selection: new vscode.Range(position, position) });
}
}),
vscode.commands.registerCommand('references-view.pickFromHistory', async () => {
interface HistoryPick extends vscode.QuickPickItem {
item: HistoryItem;
}
const entries = await this.getChildren();
const picks = entries.map(item => <HistoryPick>{
label: item.word,
description: item.description,
item
});
const pick = await vscode.window.showQuickPick(picks, { placeHolder: 'Select previous reference search' });
if (pick) {
this._reRunHistoryItem(pick.item);
}
}),
);
}
dispose(): void {
vscode.Disposable.from(...this._disposables).dispose();
this._onDidChangeTreeData.dispose();
}
private _reRunHistoryItem(item: HistoryItem): void {
this._inputs.delete(item.key);
const newPosition = item.anchor.guessedTrackedPosition();
let newInput = item.input;
// create a new input when having a tracked position which is
// different than the original position.
if (newPosition && !item.input.location.range.start.isEqual(newPosition)) {
newInput = item.input.with(new vscode.Location(item.input.location.uri, newPosition));
}
this._tree.setInput(newInput);
}
async add(input: SymbolTreeInput<unknown>) {
const doc = await vscode.workspace.openTextDocument(input.location.uri);
const anchor = new WordAnchor(doc, input.location.range.start);
const range = doc.getWordRangeAtPosition(input.location.range.start) ?? doc.getWordRangeAtPosition(input.location.range.start, /[^\s]+/);
const word = range ? doc.getText(range) : '???';
const item = new HistoryItem(JSON.stringify([range?.start ?? input.location.range.start, input.location.uri, input.title]), word, anchor, input);
// use filo-ordering of native maps
this._inputs.delete(item.key);
this._inputs.set(item.key, item);
this._ctxHasHistory.set(true);
}
clear(): void {
this._inputs.clear();
this._ctxHasHistory.set(false);
this._onDidChangeTreeData.fire(undefined);
}
get size() {
return this._inputs.size;
}
// --- tree data provider
getTreeItem(item: HistoryItem): vscode.TreeItem {
const result = new vscode.TreeItem(item.word);
result.description = item.description;
result.command = { command: '_references-view.showHistoryItem', arguments: [item], title: 'Rerun' };
result.collapsibleState = vscode.TreeItemCollapsibleState.None;
result.contextValue = 'history-item';
return result;
}
getChildren() {
return Promise.all([...this._inputs.values()].reverse());
}
getParent() {
return undefined;
}
}

View file

@ -0,0 +1,78 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { SymbolsTree } from '../tree';
import { ContextKey } from '../utils';
import { TypeHierarchyDirection, TypeItem, TypesTreeInput } from './model';
export function register(tree: SymbolsTree, context: vscode.ExtensionContext): void {
const direction = new RichTypesDirection(context.workspaceState, TypeHierarchyDirection.Subtypes);
function showTypeHierarchy() {
if (vscode.window.activeTextEditor) {
const input = new TypesTreeInput(new vscode.Location(vscode.window.activeTextEditor.document.uri, vscode.window.activeTextEditor.selection.active), direction.value);
tree.setInput(input);
}
}
function setTypeHierarchyDirection(value: TypeHierarchyDirection, anchor: TypeItem | unknown) {
direction.value = value;
let newInput: TypesTreeInput | undefined;
const oldInput = tree.getInput();
if (anchor instanceof TypeItem) {
newInput = new TypesTreeInput(new vscode.Location(anchor.item.uri, anchor.item.selectionRange.start), direction.value);
} else if (oldInput instanceof TypesTreeInput) {
newInput = new TypesTreeInput(oldInput.location, direction.value);
}
if (newInput) {
tree.setInput(newInput);
}
}
context.subscriptions.push(
vscode.commands.registerCommand('references-view.showTypeHierarchy', showTypeHierarchy),
vscode.commands.registerCommand('references-view.showSupertypes', (item: TypeItem | unknown) => setTypeHierarchyDirection(TypeHierarchyDirection.Supertypes, item)),
vscode.commands.registerCommand('references-view.showSubtypes', (item: TypeItem | unknown) => setTypeHierarchyDirection(TypeHierarchyDirection.Subtypes, item)),
vscode.commands.registerCommand('references-view.removeTypeItem', removeTypeItem)
);
}
function removeTypeItem(item: TypeItem | unknown): void {
if (item instanceof TypeItem) {
item.remove();
}
}
class RichTypesDirection {
private static _key = 'references-view.typeHierarchyMode';
private _ctxMode = new ContextKey<TypeHierarchyDirection>('references-view.typeHierarchyMode');
constructor(
private _mem: vscode.Memento,
private _value: TypeHierarchyDirection = TypeHierarchyDirection.Subtypes,
) {
const raw = _mem.get<TypeHierarchyDirection>(RichTypesDirection._key);
if (typeof raw === 'string') {
this.value = raw;
} else {
this.value = _value;
}
}
get value() {
return this._value;
}
set value(value: TypeHierarchyDirection) {
this._value = value;
this._ctxMode.set(value);
this._mem.update(RichTypesDirection._key, value);
}
}

View file

@ -0,0 +1,197 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { SymbolItemDragAndDrop, SymbolItemEditorHighlights, SymbolItemNavigation, SymbolTreeInput } from '../references-view';
import { asResourceUrl, del, getThemeIcon, tail } from '../utils';
export class TypesTreeInput implements SymbolTreeInput<TypeItem> {
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.TypeHierarchyItem[]>('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,
highlights: model,
dnd: 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);
}
}
class TypesModel implements SymbolItemNavigation<TypeItem>, SymbolItemEditorHighlights<TypeItem>, SymbolItemDragAndDrop<TypeItem> {
readonly roots: TypeItem[] = [];
private readonly _onDidChange = new vscode.EventEmitter<TypesModel>();
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<TypeItem[]> {
if (this.direction === TypeHierarchyDirection.Supertypes) {
const types = await vscode.commands.executeCommand<vscode.TypeHierarchyItem[]>('vscode.provideSupertypes', currentType.item);
return types ? types.map(item => new TypeItem(this, item, currentType)) : [];
} else {
const types = await vscode.commands.executeCommand<vscode.TypeHierarchyItem[]>('vscode.provideSubtypes', currentType.item);
return types ? types.map(item => new TypeItem(this, item, currentType)) : [];
}
}
async getTypeChildren(item: TypeItem): Promise<TypeItem[]> {
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): TypeItem | void {
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<TypeItem> {
private readonly _emitter = new vscode.EventEmitter<TypeItem | undefined>();
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 = getThemeIcon(element.item.kind);
item.command = {
command: 'vscode.open',
title: 'Open Type',
arguments: [
element.item.uri,
<vscode.TextDocumentShowOptions>{ 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;
}
}

View file

@ -0,0 +1,136 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
export function del<T>(array: T[], e: T): void {
const idx = array.indexOf(e);
if (idx >= 0) {
array.splice(idx, 1);
}
}
export function tail<T>(array: T[]): T | undefined {
return array[array.length - 1];
}
export 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}` });
}
export async function 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 function getPreviewChunks(doc: vscode.TextDocument, range: vscode.Range, beforeLen: number = 8, trim: boolean = true) {
let previewStart = range.start.with({ character: Math.max(0, range.start.character - beforeLen) });
let wordRange = doc.getWordRangeAtPosition(previewStart);
let before = doc.getText(new vscode.Range(wordRange ? wordRange.start : previewStart, range.start));
let inside = doc.getText(range);
let previewEnd = range.end.translate(0, 331);
let after = doc.getText(new vscode.Range(range.end, previewEnd));
if (trim) {
before = before.replace(/^\s*/g, '');
after = after.replace(/\s*$/g, '');
}
return { before, inside, after };
}
export class ContextKey<V> {
constructor(readonly name: string) { }
async set(value: V) {
await vscode.commands.executeCommand('setContext', this.name, value);
}
async reset() {
await vscode.commands.executeCommand('setContext', this.name, undefined);
}
}
export class WordAnchor {
private readonly _version: number;
private readonly _word: string | undefined;
constructor(private readonly _doc: vscode.TextDocument, private readonly _position: vscode.Position) {
this._version = _doc.version;
this._word = this._getAnchorWord(_doc, _position);
}
private _getAnchorWord(doc: vscode.TextDocument, pos: vscode.Position): string | undefined {
const range = doc.getWordRangeAtPosition(pos) || doc.getWordRangeAtPosition(pos, /[^\s]+/);
return range && doc.getText(range);
}
guessedTrackedPosition(): vscode.Position | undefined {
// funky entry
if (!this._word) {
return this._position;
}
// no changes
if (this._version === this._doc.version) {
return this._position;
}
// no changes here...
const wordNow = this._getAnchorWord(this._doc, this._position);
if (this._word === wordNow) {
return this._position;
}
// changes: search _word downwards and upwards
const startLine = this._position.line;
let i = 0;
let line: number;
let checked: boolean;
do {
checked = false;
// nth line down
line = startLine + i;
if (line < this._doc.lineCount) {
checked = true;
let ch = this._doc.lineAt(line).text.indexOf(this._word);
if (ch >= 0) {
return new vscode.Position(line, ch);
}
}
i += 1;
// nth line up
line = startLine - i;
if (line >= 0) {
checked = true;
let ch = this._doc.lineAt(line).text.indexOf(this._word);
if (ch >= 0) {
return new vscode.Position(line, ch);
}
}
} while (i < 100 && checked);
// fallback
return this._position;
}
}
// vscode.SymbolKind.File === 0, Module === 1, etc...
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 getThemeIcon(kind: vscode.SymbolKind): vscode.ThemeIcon | undefined {
let id = _themeIconIds[kind];
return id ? new vscode.ThemeIcon(id) : undefined;
}

View file

@ -0,0 +1,10 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "./out",
},
"include": [
"src/**/*",
"../../src/vscode-dts/vscode.d.ts"
]
}

View file

@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/node@16.x":
version "16.11.33"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.33.tgz#566713b1b626f781c5c58fe3531307283e00720c"
integrity sha512-0PJ0vg+JyU0MIan58IOIFRtSvsb7Ri+7Wltx2qAg94eMOrpg4+uuP3aUHCpxXc1i0jCXiC+zIamSZh3l9AbcQA==

View file

@ -29,21 +29,6 @@
"urlProtocol": "code-oss",
"webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/181b43c0e2949e36ecb623d8cc6de29d4fa2bae8/out/vs/workbench/contrib/webview/browser/pre/",
"builtInExtensions": [
{
"name": "ms-vscode.references-view",
"version": "0.0.89",
"repo": "https://github.com/microsoft/vscode-references-view",
"metadata": {
"id": "dc489f46-520d-4556-ae85-1f9eab3c412d",
"publisherId": {
"publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee",
"publisherName": "ms-vscode",
"displayName": "Microsoft",
"flags": "verified"
},
"publisherDisplayName": "Microsoft"
}
},
{
"name": "ms-vscode.js-debug-companion",
"version": "1.0.18",