diff --git a/package.json b/package.json index 23dbf37..9e9844a 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Mise VSCode", "publisher": "hverlin", "description": "VSCode extension for mise (manged dev tools, tasks and environment variables)", - "version": "0.0.4", + "version": "0.0.5", "repository": { "type": "git", "url": "https://github.com/hverlin/mise-vscode" @@ -63,6 +63,10 @@ { "command": "mise.runTask", "title": "Run Mise Task" + }, + { + "command": "mise.openToolDefinition", + "title": "Open Tool Definition" } ], "menus": { diff --git a/src/extension.ts b/src/extension.ts index 6b826cb..4556ecc 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -6,7 +6,7 @@ import { MiseTasksProvider, registerMiseCommands, } from "./providers/tasksProvider"; -import { MiseToolsProvider } from "./providers/toolsProvider"; +import { MiseToolsProvider, registerCommands } from "./providers/toolsProvider"; let statusBarItem: vscode.StatusBarItem; @@ -25,6 +25,7 @@ export function activate(context: vscode.ExtensionContext) { statusBarItem.text = "$(tools) Mise"; statusBarItem.tooltip = "Click to refresh Mise"; registerMiseCommands(context, tasksProvider); + registerCommands(context); vscode.window.registerTreeDataProvider("miseTasksView", tasksProvider); vscode.window.registerTreeDataProvider("miseToolsView", toolsProvider); diff --git a/src/miseService.ts b/src/miseService.ts index 043ceaf..3459077 100644 --- a/src/miseService.ts +++ b/src/miseService.ts @@ -46,6 +46,7 @@ export class MiseService { active: tool.active, installed: tool.installed, install_path: tool.install_path, + source: tool.source, } satisfies MiseTool; }); }); diff --git a/src/providers/tasksProvider.ts b/src/providers/tasksProvider.ts index 5aa8f6f..26e2981 100644 --- a/src/providers/tasksProvider.ts +++ b/src/providers/tasksProvider.ts @@ -84,7 +84,6 @@ class TaskItem extends vscode.TreeItem { this.tooltip = `Task: ${task.name}\nSource: ${task.source}\nDescription: ${task.description}`; this.iconPath = new vscode.ThemeIcon("play"); - // Add command to run the task this.command = { title: "Run Task", command: "mise.runTask", diff --git a/src/providers/toolsProvider.ts b/src/providers/toolsProvider.ts index 041c959..c2f9510 100644 --- a/src/providers/toolsProvider.ts +++ b/src/providers/toolsProvider.ts @@ -1,13 +1,14 @@ +import * as path from "node:path"; import * as vscode from "vscode"; import type { MiseService } from "../miseService"; -export class MiseToolsProvider implements vscode.TreeDataProvider { - private _onDidChangeTreeData: vscode.EventEmitter< - ToolItem | undefined | null | void - > = new vscode.EventEmitter(); - readonly onDidChangeTreeData: vscode.Event< - ToolItem | undefined | null | void - > = this._onDidChangeTreeData.event; +type TreeItem = SourceItem | ToolItem; + +export class MiseToolsProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = new vscode.EventEmitter< + TreeItem | undefined | null | void + >(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; constructor(private miseService: MiseService) {} @@ -15,24 +16,126 @@ export class MiseToolsProvider implements vscode.TreeDataProvider { this._onDidChangeTreeData.fire(); } - getTreeItem(element: ToolItem): vscode.TreeItem { + getTreeItem(element: TreeItem): vscode.TreeItem { return element; } - async getChildren(): Promise { - const tools = await this.miseService.getTools(); - return tools.map((tool) => new ToolItem(tool)); + async getChildren(element?: TreeItem): Promise { + if (!element) { + const tools = await this.miseService.getTools(); + const toolsBySource = this.groupToolsBySource(tools); + + return Object.entries(toolsBySource).map( + ([source, tools]) => new SourceItem(source, tools), + ); + } + + if (element instanceof SourceItem) { + return element.tools.map((tool) => new ToolItem(tool)); + } + + return []; + } + + private groupToolsBySource(tools: MiseTool[]): Record { + return tools.reduce((acc: Record, tool: MiseTool) => { + const source = tool.source?.path || "Unknown"; + if (!acc[source]) { + acc[source] = []; + } + acc[source].push(tool); + return acc; + }, {}); + } +} + +class SourceItem extends vscode.TreeItem { + constructor( + public readonly source: string, + public readonly tools: MiseTool[], + ) { + super(path.basename(source), vscode.TreeItemCollapsibleState.Expanded); + + this.tooltip = `Source: ${source} +Number of tools: ${tools.length}`; + + this.iconPath = new vscode.ThemeIcon("folder"); + this.contextValue = "source"; + this.description = `(${tools.length} tools)`; } } class ToolItem extends vscode.TreeItem { constructor(tool: MiseTool) { super(`${tool.name} ${tool.version}`, vscode.TreeItemCollapsibleState.None); + this.tooltip = `Tool: ${tool.name} Version: ${tool.version} Requested Version: ${tool.requested_version} +Source: ${tool.source?.path || "Unknown"} Activated: ${tool.active} Installed: ${tool.installed} Install Path: ${tool.install_path}`; + + this.iconPath = this.getToolIcon(tool); + + if (tool.source?.path) { + this.command = { + command: "mise.openToolDefinition", + title: "Open Tool Definition", + arguments: [tool], + }; + } + } + + private getToolIcon(tool: MiseTool): vscode.ThemeIcon { + if (!tool.installed) { + return new vscode.ThemeIcon("circle-outline"); + } + if (tool.active) { + return new vscode.ThemeIcon("check"); + } + return new vscode.ThemeIcon("circle-filled"); } } + +export function registerCommands(context: vscode.ExtensionContext) { + context.subscriptions.push( + vscode.commands.registerCommand( + "mise.openToolDefinition", + async (tool: MiseTool) => { + if (!tool.source?.path) { + return; + } + + const document = await vscode.workspace.openTextDocument( + tool.source.path, + ); + const editor = await vscode.window.showTextDocument(document); + + let line = 0; + for (let i = 0; i < editor.document.getText().split("\n").length; i++) { + const l = editor.document.getText().split("\n")[i]; + const [firstWord] = l.replace(/\s/g, "").replace(/"/g, "").split("="); + if (firstWord === tool.name) { + line = i + 1; + break; + } + } + + if (line) { + const position = new vscode.Position(Math.max(0, line - 1), 0); + const position2 = new vscode.Position( + Math.max(0, line - 1), + tool.name.includes(":") ? tool.name.length + 2 : tool.name.length, + ); + editor.selection = new vscode.Selection(position, position2); + editor.revealRange( + new vscode.Range(position, position2), + vscode.TextEditorRevealType.InCenter, + ); + } + }, + ), + ); +} diff --git a/src/types.ts b/src/types.ts index 025697d..ea040f2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,9 +4,15 @@ type MiseTask = { description: string; }; +type MiseToolSource = { + type: string; + path: string; +}; + type MiseTool = { name: string; version: string; + source?: MiseToolSource; requested_version: string; installed: boolean; active: boolean;