Skip to content

Commit

Permalink
feat: implement "go to definition" for task
Browse files Browse the repository at this point in the history
  • Loading branch information
hverlin committed Jan 12, 2025
1 parent 3c10b48 commit 2e23343
Show file tree
Hide file tree
Showing 8 changed files with 361 additions and 93 deletions.
8 changes: 8 additions & 0 deletions src/miseExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
import { createMenu } from "./extensionMenu";
import { MiseFileWatcher } from "./miseFileWatcher";
import { MiseService } from "./miseService";
import { TaskDefinitionProvider } from "./providers/TaskDefinitionProvider";
import { ToolCompletionProvider } from "./providers/ToolCompletionProvider";
import { WorkspaceDecorationProvider } from "./providers/WorkspaceDecorationProvider";
import {
Expand Down Expand Up @@ -363,6 +364,13 @@ export class MiseExtension {
),
);

context.subscriptions.push(
vscode.languages.registerDefinitionProvider(
allTomlFilesSelector,
new TaskDefinitionProvider(this.miseService),
),
);

registerTomlFileLinks(context);

context.subscriptions.push(
Expand Down
8 changes: 6 additions & 2 deletions src/miseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,14 +310,18 @@ export class MiseService {
await this.cache.execCmd({ command: "trust", setMiseEnv: false });
}

async getTasks(): Promise<MiseTask[]> {
async getTasks(
{ includeHidden }: { includeHidden?: boolean } = {
includeHidden: false,
},
): Promise<MiseTask[]> {
if (!this.getMiseBinaryPath()) {
return [];
}

try {
const { stdout } = await this.cache.execCmd({
command: "tasks ls --json",
command: includeHidden ? "tasks ls --json --hidden" : "tasks ls --json",
});
return JSON.parse(stdout);
} catch (error: unknown) {
Expand Down
77 changes: 77 additions & 0 deletions src/providers/TaskDefinitionProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import os from "node:os";
import vscode from "vscode";
import { isMiseExtensionEnabled } from "../configuration";
import type { MiseService } from "../miseService";
import { expandPath } from "../utils/fileUtils";
import {
type MiseTomlType,
TomlParser,
findTaskDefinition,
} from "../utils/miseFileParser";
import { DEPENDS_KEYWORDS } from "../utils/miseUtilts";

export class TaskDefinitionProvider implements vscode.DefinitionProvider {
private miseService: MiseService;
constructor(miseService: MiseService) {
this.miseService = miseService;
}

public async provideDefinition(
document: vscode.TextDocument,
position: vscode.Position,
): Promise<vscode.LocationLink[]> {
if (!isMiseExtensionEnabled()) {
return [];
}

const tasks = await this.miseService.getTasks({ includeHidden: true });
const tasksSources = tasks.map((t) => expandPath(t.source));
if (!tasksSources.includes(document.uri.fsPath)) {
return [];
}

const tomParser = new TomlParser<MiseTomlType>(document.getText());

const keyAtPosition = tomParser.getKeyAtPosition(position);
const keyPath = keyAtPosition?.key ?? [];
if (!keyPath.length) {
return [];
}

if (!DEPENDS_KEYWORDS.includes(keyPath.at(-1) || "")) {
return [];
}

const taskNameRange = document.getWordRangeAtPosition(position, /[\w:-]+/);
if (!taskNameRange) {
return [];
}

const taskName = document.getText(taskNameRange);

const task = tasks.find((t) => t.name === taskName);
if (!task) {
return [];
}

const uri = vscode.Uri.file(task.source.replace(/^~/, os.homedir()));
const taskDocument = await vscode.workspace.openTextDocument(uri);

const foundPosition = findTaskDefinition(taskDocument, task.name);

return [
{
originSelectionRange: taskNameRange,
targetUri: vscode.Uri.parse(task.source),
targetSelectionRange: new vscode.Range(
foundPosition.start,
foundPosition.end,
),
targetRange: new vscode.Range(
foundPosition.start,
foundPosition.end.translate(100, 100), // hack to make the range visible, improve later
),
},
];
}
}
6 changes: 3 additions & 3 deletions src/providers/miseCompletionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ export class MiseCompletionProvider implements vscode.CompletionItemProvider {
return [];
}

const tasks = await this.miseService.getTasks();
const tasks = await this.miseService.getTasks({ includeHidden: true });
if (!this.tasksCache.length && tasks.length) {
this.tasksCache = await this.miseService.getTasks();
this.tasksCache = tasks;
}

return this.tasksCache
Expand All @@ -50,7 +50,7 @@ export class MiseCompletionProvider implements vscode.CompletionItemProvider {
}

private isDependsArrayContext(lineText: string, position: number): boolean {
const dependsMatch = /(depends|wait_for)\s*=/.test(lineText);
const dependsMatch = /(depends|wait_for|depends_post)\s*=/.test(lineText);
if (!dependsMatch) {
return false;
}
Expand Down
33 changes: 17 additions & 16 deletions src/providers/tasksProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
setupTaskFile,
} from "../utils/fileUtils";
import { logger } from "../utils/logger";
import { findTaskPosition } from "../utils/miseFileParser";
import { findTaskDefinition } from "../utils/miseFileParser";
import {
allowedFileTaskDirs,
idiomaticFiles,
Expand Down Expand Up @@ -398,22 +398,23 @@ export function registerTasksCommands(
const document = await vscode.workspace.openTextDocument(uri);
const editor = await vscode.window.showTextDocument(document);

if (!document.fileName.endsWith(".toml")) {
editor.revealRange(
new vscode.Range(0, 0, 0, 0),
vscode.TextEditorRevealType.InCenter,
);
editor.selection = new vscode.Selection(0, 0, 0, 0);
return;
}

const position = findTaskPosition(document, selectedTask.name);
const position = findTaskDefinition(document, selectedTask.name);
if (position) {
const range = document.lineAt(position.line).range;
const startOfLine = new vscode.Position(position.line, 0);
const selection = new vscode.Selection(startOfLine, range.end);
editor.revealRange(range, vscode.TextEditorRevealType.InCenter);
editor.selection = selection;
if (position.start.isEqual(position.end)) {
editor.selection = new vscode.Selection(
position.start,
position.start,
);
editor.revealRange(
new vscode.Range(position.start, position.start),
);
} else {
const startOfLine = new vscode.Position(position.start.line, 0);
const range = document.lineAt(position.start.line).range;
const selection = new vscode.Selection(startOfLine, range.end);
editor.revealRange(range, vscode.TextEditorRevealType.InCenter);
editor.selection = selection;
}
} else {
vscode.window.showWarningMessage(
`Could not locate task "${selectedTask.name}" in ${document.fileName}`,
Expand Down
96 changes: 96 additions & 0 deletions src/utils/miseFileParser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { describe, expect, it } from "bun:test";

import { type MiseTomlType, TomlParser } from "./miseFileParser";

describe("miseFileParser", () => {
it("mise.toml", () => {
const tomlParser = new TomlParser<MiseTomlType>(
`
[tasks.example]
depends = [
"example2"
]
`.trim(),
);

expect(tomlParser.parsed).toEqual({
tasks: { example: { depends: ["example2"] } },
});

expect(tomlParser.getAllPositions()).toEqual([
{
keyStart: { line: 0, character: 0 },
keyEnd: { line: 0, character: 5 },
valueStart: { line: 0, character: 0 },
valueEnd: { line: 0, character: 5 },
key: ["tasks"],
value: { example: { depends: ["example2"] } },
},
{
keyStart: { line: 0, character: 6 },
keyEnd: { line: 0, character: 13 },
valueStart: { line: 0, character: 0 },
valueEnd: { line: 0, character: 14 },
key: ["tasks", "example"],
value: { depends: ["example2"] },
},
{
keyStart: { line: 1, character: 0 },
keyEnd: { line: 1, character: 7 },
valueStart: { line: 1, character: 10 },
valueEnd: { line: 3, character: 1 },
key: ["tasks", "example", "depends"],
value: ["example2"],
},
]);

expect(tomlParser.getKeyAtPosition({ line: 0, character: 0 })).toEqual({
keyStart: { line: 0, character: 6 },
keyEnd: { line: 0, character: 13 },
valueStart: { line: 0, character: 0 },
valueEnd: { line: 0, character: 14 },
key: ["tasks", "example"],
value: { depends: ["example2"] },
});

expect(tomlParser.getKeyAtPosition({ line: 2, character: 5 })).toEqual({
keyStart: { line: 1, character: 0 },
keyEnd: { line: 1, character: 7 },
valueStart: { line: 1, character: 10 },
valueEnd: { line: 3, character: 1 },
key: ["tasks", "example", "depends"],
value: ["example2"],
});
});

it("task files", () => {
const tomlParser = new TomlParser<object>(
`
ci = { depends = ["format", "build", "test"] }
`.trim(),
);

expect(tomlParser.parsed).toEqual({
ci: { depends: ["format", "build", "test"] },
});

expect(tomlParser.getAllPositions()).toEqual([
{
keyStart: { line: 0, character: 0 },
keyEnd: { line: 0, character: 1 },
valueStart: { line: 0, character: 4 },
valueEnd: { line: 0, character: 45 },
key: ["ci"],
value: { depends: ["format", "build", "test"] },
},
{
keyStart: { line: 0, character: 6 },
keyEnd: { line: 0, character: 13 },
valueStart: { line: 0, character: 16 },
valueEnd: { line: 0, character: 43 },
key: ["ci", "depends"],
value: ["format", "build", "test"],
},
]);
});
});
Loading

0 comments on commit 2e23343

Please sign in to comment.