Skip to content

Commit 324ccfa

Browse files
feat: (add symbol provider) (#182)
* feat: add MiseTomlTaskSymbolProvider for mise task symbol support
1 parent 30e2830 commit 324ccfa

3 files changed

Lines changed: 424 additions & 0 deletions

File tree

src/miseExtension.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
} from "./providers/envProvider";
4343
import { addToolInfoToEditor } from "./providers/inlineToolDecorator";
4444
import { MiseTextDocumentContentProvider } from "./providers/MiseTextDocumentContentProvider";
45+
import { MiseTomlTaskSymbolProvider } from "./providers/MiseTomlTaskSymbolProvider";
4546
import { MiseCompletionProvider } from "./providers/miseCompletionProvider";
4647
import { MiseFileTaskCodeLensProvider } from "./providers/miseFileTaskCodeLensProvider";
4748
import {
@@ -636,6 +637,13 @@ export class MiseExtension {
636637
),
637638
);
638639

640+
context.subscriptions.push(
641+
vscode.languages.registerDocumentSymbolProvider(
642+
allTomlFilesSelector,
643+
new MiseTomlTaskSymbolProvider(vscode),
644+
),
645+
);
646+
639647
await vscode.commands.executeCommand(MISE_RELOAD);
640648

641649
setTimeout(() => {
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
import { beforeEach, describe, expect, it } from "bun:test";
2+
import * as vscode from "vscode";
3+
import { MiseTomlTaskSymbolProvider } from "./MiseTomlTaskSymbolProvider";
4+
5+
class MockDocumentSymbol {
6+
constructor(
7+
public name: string,
8+
public detail: string,
9+
public kind: vscode.SymbolKind,
10+
public range: vscode.Range,
11+
public selectionRange: vscode.Range,
12+
) {}
13+
}
14+
15+
const MockSymbolKind = {
16+
Property: 1,
17+
Function: 2,
18+
Namespace: 3,
19+
};
20+
21+
class VSCodeMock {
22+
DocumentSymbol = MockDocumentSymbol;
23+
Range = vscode.Range;
24+
Position = vscode.Position;
25+
SymbolKind = MockSymbolKind;
26+
}
27+
28+
let provider: MiseTomlTaskSymbolProvider;
29+
30+
const multiSymbolMiseToml = `
31+
[tools]
32+
key_tool = "value_tool"
33+
34+
[env]
35+
env_key = "value_env"
36+
env_key2 = "value_env2"
37+
38+
[settings]
39+
setting_key = "value_setting"
40+
41+
[tasks]
42+
key1 = "value1"
43+
"key2"= "value2"
44+
"complex \\"key\\" 3" = "value3"
45+
46+
[tasks.example]
47+
key_example = "value4"
48+
49+
[tasks."complex [\\"section\\"] name"]
50+
key_complex = "value5"
51+
52+
[other]
53+
key_other = "value6"
54+
`.trim();
55+
56+
const simpleValidMiseToml = `
57+
[tools]
58+
key_tool = "value_tool"
59+
60+
[env]
61+
env_key = "value_env"
62+
`.trim();
63+
64+
const simpleInvalidMiseToml = `
65+
[tools
66+
key_tool = "value_tool"
67+
68+
[env]
69+
env_key = "value_env"
70+
`.trim();
71+
72+
describe("MiseTomlTaskSymbolProvider tests", () => {
73+
beforeEach(() => {
74+
// Mock the logger to prevent actual logging during tests
75+
/* eslint-disable @typescript-eslint/no-explicit-any */
76+
provider = new MiseTomlTaskSymbolProvider(
77+
new VSCodeMock() as typeof vscode,
78+
);
79+
});
80+
it("provides symbols for tasks sections and trivial key-value pairs", () => {
81+
const document = {
82+
getText: () => multiSymbolMiseToml,
83+
} as vscode.TextDocument;
84+
const toolsSectionLength = "[tools]".length;
85+
const envSectionLength = "[env]".length;
86+
const settinsSectionLength = "[settings]".length;
87+
88+
const key1Length = 'key1 = "value1"'.length;
89+
const key2Length = '"key2"= "value2"'.length;
90+
const complexKey3Length = '"complex \\"key\\" 3" = "value3"'.length;
91+
92+
const exampleSectionLength = "[tasks.example]".length;
93+
const complexSectionLength = '[tasks."complex [\\"section\\"] name"]'
94+
.length;
95+
96+
const symbols = provider.provideDocumentSymbols(
97+
document,
98+
{} as vscode.CancellationToken,
99+
) as MockDocumentSymbol[];
100+
101+
expect(symbols).toEqual([
102+
new MockDocumentSymbol(
103+
"[tools]",
104+
"",
105+
new VSCodeMock().SymbolKind.Namespace,
106+
new vscode.Range(
107+
new vscode.Position(0, 0),
108+
new vscode.Position(0, toolsSectionLength),
109+
),
110+
new vscode.Range(
111+
new vscode.Position(0, 0),
112+
new vscode.Position(0, toolsSectionLength),
113+
),
114+
),
115+
// only trivial key-value pairs should be included as symbols from "tasks" section
116+
new MockDocumentSymbol(
117+
"[env]",
118+
"",
119+
new VSCodeMock().SymbolKind.Namespace,
120+
new vscode.Range(
121+
new vscode.Position(3, 0),
122+
new vscode.Position(3, envSectionLength),
123+
),
124+
new vscode.Range(
125+
new vscode.Position(3, 0),
126+
new vscode.Position(3, envSectionLength),
127+
),
128+
),
129+
new MockDocumentSymbol(
130+
"[settings]",
131+
"",
132+
new VSCodeMock().SymbolKind.Namespace,
133+
new vscode.Range(
134+
new vscode.Position(7, 0),
135+
new vscode.Position(7, settinsSectionLength),
136+
),
137+
new vscode.Range(
138+
new vscode.Position(7, 0),
139+
new vscode.Position(7, settinsSectionLength),
140+
),
141+
),
142+
new MockDocumentSymbol(
143+
"key1",
144+
"value1",
145+
new VSCodeMock().SymbolKind.Property,
146+
new vscode.Range(
147+
new vscode.Position(11, 0),
148+
new vscode.Position(11, key1Length),
149+
),
150+
new vscode.Range(
151+
new vscode.Position(11, 0),
152+
new vscode.Position(11, key1Length),
153+
),
154+
),
155+
new MockDocumentSymbol(
156+
"key2",
157+
"value2",
158+
new VSCodeMock().SymbolKind.Property,
159+
new vscode.Range(
160+
new vscode.Position(12, 0),
161+
new vscode.Position(12, key2Length),
162+
),
163+
new vscode.Range(
164+
new vscode.Position(12, 0),
165+
new vscode.Position(12, key2Length),
166+
),
167+
),
168+
new MockDocumentSymbol(
169+
'complex "key" 3',
170+
"value3",
171+
new VSCodeMock().SymbolKind.Property,
172+
new vscode.Range(
173+
new vscode.Position(13, 0),
174+
new vscode.Position(13, complexKey3Length),
175+
),
176+
new vscode.Range(
177+
new vscode.Position(13, 0),
178+
new vscode.Position(13, complexKey3Length),
179+
),
180+
),
181+
182+
// "example" and "complex [\"section\"] name" sections should be included as symbols
183+
new MockDocumentSymbol(
184+
"example",
185+
"",
186+
new VSCodeMock().SymbolKind.Function,
187+
new vscode.Range(
188+
new vscode.Position(15, 0),
189+
new vscode.Position(15, exampleSectionLength),
190+
),
191+
new vscode.Range(
192+
new vscode.Position(15, 0),
193+
new vscode.Position(15, exampleSectionLength),
194+
),
195+
),
196+
new MockDocumentSymbol(
197+
'complex ["section"] name',
198+
"",
199+
new VSCodeMock().SymbolKind.Function,
200+
new vscode.Range(
201+
new vscode.Position(18, 0),
202+
new vscode.Position(18, complexSectionLength),
203+
),
204+
new vscode.Range(
205+
new vscode.Position(18, 0),
206+
new vscode.Position(18, complexSectionLength),
207+
),
208+
),
209+
210+
// "other" section should be ignored, as it's not a "tasks" section
211+
]);
212+
});
213+
214+
it("keeps previous symbols if cancellation is requested", () => {
215+
const document = {
216+
getText: () => simpleValidMiseToml,
217+
} as vscode.TextDocument;
218+
219+
// First call to populate symbols
220+
const initialSymbols = provider.provideDocumentSymbols(
221+
document,
222+
{} as vscode.CancellationToken,
223+
) as MockDocumentSymbol[];
224+
225+
// Simulate cancellation on second call
226+
const cancelledSymbols = provider.provideDocumentSymbols(document, {
227+
isCancellationRequested: true,
228+
} as vscode.CancellationToken) as MockDocumentSymbol[];
229+
230+
expect(cancelledSymbols).toBe(initialSymbols);
231+
});
232+
233+
it("returns empty symbols if TOML parsing fails and no previous symbols exist", () => {
234+
const invalidDocument = {
235+
getText: () => simpleInvalidMiseToml,
236+
} as vscode.TextDocument;
237+
238+
const symbols = provider.provideDocumentSymbols(
239+
invalidDocument,
240+
{} as vscode.CancellationToken,
241+
) as MockDocumentSymbol[];
242+
243+
expect(symbols).toEqual([]);
244+
});
245+
246+
it("keeps last valid symbols if TOML parsing fails", () => {
247+
const validDocument = {
248+
getText: () => simpleValidMiseToml,
249+
} as vscode.TextDocument;
250+
251+
const invalidDocument = {
252+
getText: () => simpleInvalidMiseToml,
253+
} as vscode.TextDocument;
254+
255+
// First call with valid TOML to populate symbols
256+
const validSymbols = provider.provideDocumentSymbols(
257+
validDocument,
258+
{} as vscode.CancellationToken,
259+
) as MockDocumentSymbol[];
260+
// Second call with invalid TOML should keep previous symbols
261+
const invalidSymbols = provider.provideDocumentSymbols(
262+
invalidDocument,
263+
{} as vscode.CancellationToken,
264+
) as MockDocumentSymbol[];
265+
266+
expect(invalidSymbols).toBe(validSymbols);
267+
});
268+
});

0 commit comments

Comments
 (0)