@mrclrchtr/supi-lsp 1.16.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -56
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/package.json +6 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/abort-utils.ts +31 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/api.ts +8 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +10 -4
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/config.ts +1 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +1 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/index.ts +6 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/model-selection.ts +134 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +9 -2
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +109 -4
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +5 -1
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/spinner-frames.ts +11 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/node_modules/@mrclrchtr/supi-core/src/status-spinner.ts +68 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/package.json +3 -2
- package/node_modules/@mrclrchtr/supi-code-runtime/src/api.ts +7 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/capability/types.ts +56 -2
- package/node_modules/@mrclrchtr/supi-code-runtime/src/evidence-badge.ts +40 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/index.ts +5 -0
- package/node_modules/@mrclrchtr/supi-code-runtime/src/types.ts +76 -3
- package/node_modules/@mrclrchtr/supi-code-runtime/src/workspace/runtime.ts +37 -2
- package/node_modules/@mrclrchtr/supi-core/package.json +6 -1
- package/node_modules/@mrclrchtr/supi-core/src/abort-utils.ts +31 -0
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +8 -0
- package/node_modules/@mrclrchtr/supi-core/src/config/config-settings.ts +10 -4
- package/node_modules/@mrclrchtr/supi-core/src/config.ts +1 -0
- package/node_modules/@mrclrchtr/supi-core/src/footer-registry.ts +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +6 -0
- package/node_modules/@mrclrchtr/supi-core/src/model-selection.ts +134 -0
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-registry.ts +9 -2
- package/node_modules/@mrclrchtr/supi-core/src/settings/settings-ui.ts +109 -4
- package/node_modules/@mrclrchtr/supi-core/src/settings-ui.ts +5 -1
- package/node_modules/@mrclrchtr/supi-core/src/spinner-frames.ts +11 -0
- package/node_modules/@mrclrchtr/supi-core/src/status-spinner.ts +68 -0
- package/package.json +4 -31
- package/src/api.ts +27 -0
- package/src/client/client-refresh.ts +5 -0
- package/src/client/client.ts +246 -1
- package/src/client/transport.ts +4 -0
- package/src/config/capabilities.ts +3 -0
- package/src/config/config.ts +4 -2
- package/src/config/lsp-settings.ts +102 -0
- package/src/config/server-actions.ts +59 -0
- package/src/config/server-config.ts +4 -0
- package/src/index.ts +11 -0
- package/src/manager/capability-index.ts +1 -1
- package/src/manager/manager-project-info.ts +2 -1
- package/src/manager/manager.ts +115 -26
- package/src/provider/lsp-semantic-provider.ts +177 -134
- package/src/provider/refactor-planning.ts +214 -0
- package/src/session/runtime-controller.ts +320 -0
- package/src/session/runtime-registration.ts +26 -5
- package/src/session/service-registry.ts +81 -4
- package/src/summary.ts +2 -0
- package/src/extension.ts +0 -1
- package/src/format.ts +0 -373
- package/src/handlers/diagnostic-injection.ts +0 -182
- package/src/handlers/session-lifecycle.ts +0 -149
- package/src/handlers/status-command.ts +0 -38
- package/src/handlers/workspace-recovery.ts +0 -92
- package/src/lsp.ts +0 -37
- package/src/session/lsp-state.ts +0 -93
- package/src/session/settings-registration.ts +0 -296
- package/src/session/tree-persist.ts +0 -75
- package/src/tool/guidance.ts +0 -69
- package/src/tool/names.ts +0 -27
- package/src/tool/overrides.ts +0 -204
- package/src/tool/register-tools.ts +0 -69
- package/src/tool/service-actions.ts +0 -399
- package/src/tool/tool-specs.ts +0 -248
- package/src/ui/renderer.ts +0 -120
- package/src/ui/ui.ts +0 -360
- package/src/workspace-change.ts +0 -40
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared model-selection helpers for SuPi extensions.
|
|
3
|
+
*
|
|
4
|
+
* Provides scoped-model listing using PI's `enabledModels` configuration,
|
|
5
|
+
* matching the same semantics as the `@mrclrchtr/supi-review` model picker.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Model } from "@earendil-works/pi-ai";
|
|
11
|
+
import { type ExtensionContext, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
|
|
13
|
+
// ── Types ──────────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
/** A selectable model entry with display metadata. */
|
|
16
|
+
export interface ModelSelection {
|
|
17
|
+
/** Canonical `provider/model-id` string. */
|
|
18
|
+
canonicalId: string;
|
|
19
|
+
/** Provider name, e.g. `"anthropic"`. */
|
|
20
|
+
provider: string;
|
|
21
|
+
/** Model id, e.g. `"claude-sonnet-4-5"`. */
|
|
22
|
+
id: string;
|
|
23
|
+
// biome-ignore lint/suspicious/noExplicitAny: Model<any> is pi's canonical type
|
|
24
|
+
model: Model<any>;
|
|
25
|
+
/** Human-readable label (model name or canonicalId). */
|
|
26
|
+
label: string;
|
|
27
|
+
/** Optional description (canonicalId when different from label). */
|
|
28
|
+
description?: string;
|
|
29
|
+
/** Whether this model is the current session model. */
|
|
30
|
+
isCurrent: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
/** Build the canonical `provider/model-id` string. */
|
|
36
|
+
export function toCanonicalModelId(
|
|
37
|
+
model: Pick<NonNullable<ExtensionContext["model"]>, "provider" | "id">,
|
|
38
|
+
): string {
|
|
39
|
+
return `${model.provider}/${model.id}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* List selectable models from PI's scoped model configuration.
|
|
44
|
+
*
|
|
45
|
+
* Only models that match the configured `enabledModels` patterns are offered.
|
|
46
|
+
* The current session model is included only when it is inside that scoped set.
|
|
47
|
+
* Returns an empty array when no scoped model patterns are configured.
|
|
48
|
+
*/
|
|
49
|
+
export function getSelectableModels(
|
|
50
|
+
ctx: Pick<ExtensionContext, "cwd" | "modelRegistry" | "model">,
|
|
51
|
+
enabledModelPatterns = SettingsManager.create(ctx.cwd).getEnabledModels(),
|
|
52
|
+
): ModelSelection[] {
|
|
53
|
+
if (!enabledModelPatterns || enabledModelPatterns.length === 0) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const byCanonicalId = new Map<string, ModelSelection>();
|
|
58
|
+
const availableModels = filterByEnabledModels(
|
|
59
|
+
enabledModelPatterns,
|
|
60
|
+
ctx.modelRegistry.getAvailable(),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const addModel = (
|
|
64
|
+
// biome-ignore lint/suspicious/noExplicitAny: Model<any> is pi's canonical type
|
|
65
|
+
model: Model<any>,
|
|
66
|
+
isCurrent: boolean,
|
|
67
|
+
) => {
|
|
68
|
+
const canonicalId = toCanonicalModelId(model);
|
|
69
|
+
const existing = byCanonicalId.get(canonicalId);
|
|
70
|
+
if (existing) {
|
|
71
|
+
if (isCurrent) existing.isCurrent = true;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
byCanonicalId.set(canonicalId, {
|
|
76
|
+
canonicalId,
|
|
77
|
+
provider: model.provider,
|
|
78
|
+
id: model.id,
|
|
79
|
+
model,
|
|
80
|
+
label: model.name ?? canonicalId,
|
|
81
|
+
description: canonicalId,
|
|
82
|
+
isCurrent,
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
if (ctx.model && matchModelPatterns(ctx.model, enabledModelPatterns)) {
|
|
87
|
+
addModel(ctx.model, true);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const model of availableModels) {
|
|
91
|
+
addModel(
|
|
92
|
+
model,
|
|
93
|
+
ctx.model ? toCanonicalModelId(model) === toCanonicalModelId(ctx.model) : false,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Array.from(byCanonicalId.values()).sort((a, b) => {
|
|
98
|
+
if (a.isCurrent !== b.isCurrent) return a.isCurrent ? -1 : 1;
|
|
99
|
+
return a.canonicalId.localeCompare(b.canonicalId);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Private helpers ────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
function filterByEnabledModels<T extends { provider: string; id: string }>(
|
|
106
|
+
patterns: string[],
|
|
107
|
+
models: T[],
|
|
108
|
+
): T[] {
|
|
109
|
+
return models.filter((model) => matchModelPatterns(model, patterns));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function matchModelPatterns(model: { provider: string; id: string }, patterns: string[]): boolean {
|
|
113
|
+
return patterns.some((pattern) => matchModelPattern(model, pattern));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function matchModelPattern(model: { provider: string; id: string }, pattern: string): boolean {
|
|
117
|
+
const canonicalId = `${model.provider}/${model.id}`;
|
|
118
|
+
if (pattern.includes("/")) {
|
|
119
|
+
return simpleGlobMatch(canonicalId, pattern);
|
|
120
|
+
}
|
|
121
|
+
return simpleGlobMatch(model.id, pattern) || simpleGlobMatch(canonicalId, pattern);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function simpleGlobMatch(text: string, pattern: string): boolean {
|
|
125
|
+
if (!pattern.includes("*") && !pattern.includes("?")) {
|
|
126
|
+
return text.toLowerCase() === pattern.toLowerCase();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const regex = pattern
|
|
130
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
131
|
+
.replace(/\*/g, ".*")
|
|
132
|
+
.replace(/\?/g, ".");
|
|
133
|
+
return new RegExp(`^${regex}$`, "i").test(text);
|
|
134
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Extensions declare their settings via `registerSettings()` during their
|
|
4
4
|
// factory function. The generic settings UI reads them via `getRegisteredSettings()`.
|
|
5
5
|
|
|
6
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
7
|
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
7
8
|
import { createRegistry } from "../registry-utils.ts";
|
|
8
9
|
|
|
@@ -14,9 +15,15 @@ export interface SettingsSection {
|
|
|
14
15
|
/** Human-readable label shown in the UI */
|
|
15
16
|
label: string;
|
|
16
17
|
/** Load current SettingItem[] for the given scope */
|
|
17
|
-
loadValues: (scope: SettingsScope, cwd: string) => SettingItem[];
|
|
18
|
+
loadValues: (scope: SettingsScope, cwd: string, ctx?: ExtensionContext) => SettingItem[];
|
|
18
19
|
/** Persist a change back to config */
|
|
19
|
-
persistChange: (
|
|
20
|
+
persistChange: (
|
|
21
|
+
scope: SettingsScope,
|
|
22
|
+
cwd: string,
|
|
23
|
+
settingId: string,
|
|
24
|
+
value: string,
|
|
25
|
+
ctx?: ExtensionContext,
|
|
26
|
+
) => void;
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
const registry = createRegistry<SettingsSection>("settings-registry");
|
|
@@ -10,10 +10,14 @@ import {
|
|
|
10
10
|
Input,
|
|
11
11
|
Key,
|
|
12
12
|
matchesKey,
|
|
13
|
+
type SelectItem,
|
|
14
|
+
SelectList,
|
|
15
|
+
type SelectListTheme,
|
|
13
16
|
type SettingItem,
|
|
14
17
|
SettingsList,
|
|
15
18
|
Text,
|
|
16
19
|
} from "@earendil-works/pi-tui";
|
|
20
|
+
import { getSelectableModels } from "../model-selection.ts";
|
|
17
21
|
import {
|
|
18
22
|
getRegisteredSettings,
|
|
19
23
|
type SettingsScope,
|
|
@@ -84,10 +88,11 @@ function buildFlatItems(
|
|
|
84
88
|
sections: SettingsSection[],
|
|
85
89
|
scope: SettingsScope,
|
|
86
90
|
cwd: string,
|
|
91
|
+
ctx?: ExtensionContext,
|
|
87
92
|
): SettingItem[] {
|
|
88
93
|
const items: SettingItem[] = [];
|
|
89
94
|
for (const section of sections) {
|
|
90
|
-
const sectionItems = section.loadValues(scope, cwd);
|
|
95
|
+
const sectionItems = section.loadValues(scope, cwd, ctx);
|
|
91
96
|
for (const item of sectionItems) {
|
|
92
97
|
items.push({
|
|
93
98
|
...item,
|
|
@@ -115,6 +120,7 @@ function findSectionAndId(
|
|
|
115
120
|
// ── Component ────────────────────────────────────────────────
|
|
116
121
|
|
|
117
122
|
interface SettingsOverlayDeps {
|
|
123
|
+
ctx: ExtensionContext;
|
|
118
124
|
state: OverlayState;
|
|
119
125
|
container: Container;
|
|
120
126
|
settingsList: SettingsList | null;
|
|
@@ -125,15 +131,21 @@ interface SettingsOverlayDeps {
|
|
|
125
131
|
|
|
126
132
|
function createSettingsList(deps: SettingsOverlayDeps): SettingsList {
|
|
127
133
|
const sections = getRegisteredSettings();
|
|
128
|
-
const items = buildFlatItems(sections, deps.state.scope, deps.state.cwd);
|
|
134
|
+
const items = buildFlatItems(sections, deps.state.scope, deps.state.cwd, deps.ctx);
|
|
129
135
|
const onChange = (flatId: string, newValue: string) => {
|
|
130
136
|
const found = findSectionAndId(sections, flatId);
|
|
131
137
|
if (found) {
|
|
132
|
-
found.section.persistChange(
|
|
138
|
+
found.section.persistChange(
|
|
139
|
+
deps.state.scope,
|
|
140
|
+
deps.state.cwd,
|
|
141
|
+
found.itemId,
|
|
142
|
+
newValue,
|
|
143
|
+
deps.ctx,
|
|
144
|
+
);
|
|
133
145
|
}
|
|
134
146
|
// Re-read all values to reflect persisted changes, but keep the list
|
|
135
147
|
// instance (and its selectedIndex) intact.
|
|
136
|
-
const updatedItems = buildFlatItems(sections, deps.state.scope, deps.state.cwd);
|
|
148
|
+
const updatedItems = buildFlatItems(sections, deps.state.scope, deps.state.cwd, deps.ctx);
|
|
137
149
|
for (const updated of updatedItems) {
|
|
138
150
|
const existing = items.find((i) => i.id === updated.id);
|
|
139
151
|
if (existing && existing.currentValue !== updated.currentValue) {
|
|
@@ -182,6 +194,98 @@ function handleScopeToggle(deps: SettingsOverlayDeps): void {
|
|
|
182
194
|
deps.tui.requestRender();
|
|
183
195
|
}
|
|
184
196
|
|
|
197
|
+
/** Minimal SelectList theme — uses identity so the parent SettingsList provides styling context. */
|
|
198
|
+
const PASSTHROUGH_THEME: SelectListTheme = {
|
|
199
|
+
selectedPrefix: (text) => `› ${text}`,
|
|
200
|
+
selectedText: (text) => text,
|
|
201
|
+
description: (text) => text,
|
|
202
|
+
scrollInfo: (text) => text,
|
|
203
|
+
noMatch: (text) => text,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Create a model picker submenu for settings.
|
|
208
|
+
*
|
|
209
|
+
* Shows a scrollable list of selectable models from the scoped model set,
|
|
210
|
+
* with the current session model annotated `[current]`. The first entry is
|
|
211
|
+
* always `"disabled"`.
|
|
212
|
+
*
|
|
213
|
+
* @param currentValue - Currently configured canonical model id or `"disabled"`.
|
|
214
|
+
* @param done - Callback invoked with the selected value, or undefined on cancel.
|
|
215
|
+
* @param ctx - Extension context for model listing. When undefined, only
|
|
216
|
+
* `"disabled"` is offered.
|
|
217
|
+
*/
|
|
218
|
+
export function createModelPickerSubmenu(
|
|
219
|
+
currentValue: string,
|
|
220
|
+
done: (selectedValue?: string) => void,
|
|
221
|
+
ctx?: ExtensionContext,
|
|
222
|
+
): {
|
|
223
|
+
render: (width: number) => string[];
|
|
224
|
+
invalidate: () => void;
|
|
225
|
+
handleInput: (data: string) => boolean;
|
|
226
|
+
} {
|
|
227
|
+
const items = buildModelItems(ctx);
|
|
228
|
+
|
|
229
|
+
const initialIndex =
|
|
230
|
+
currentValue === "disabled"
|
|
231
|
+
? 0
|
|
232
|
+
: Math.max(
|
|
233
|
+
0,
|
|
234
|
+
items.findIndex((item) => item.value === currentValue),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const container = new Container();
|
|
238
|
+
container.addChild(new Text(" Select suggestion model", 1, 0));
|
|
239
|
+
container.addChild(new Text("", 1, 0));
|
|
240
|
+
|
|
241
|
+
const selectList = new SelectList(items, Math.min(items.length, 15), PASSTHROUGH_THEME);
|
|
242
|
+
|
|
243
|
+
if (initialIndex >= 0) {
|
|
244
|
+
selectList.setSelectedIndex(initialIndex);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
selectList.onSelect = (item) => done(item.value);
|
|
248
|
+
selectList.onCancel = () => done();
|
|
249
|
+
|
|
250
|
+
container.addChild(selectList);
|
|
251
|
+
container.addChild(new Text(" ↑↓ navigate • enter select • esc cancel", 1, 0));
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
render: (width: number) => container.render(width),
|
|
255
|
+
invalidate: () => container.invalidate(),
|
|
256
|
+
handleInput: (data: string) => {
|
|
257
|
+
selectList.handleInput(data);
|
|
258
|
+
return true;
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Build selectable model items with "disabled" first. */
|
|
264
|
+
function buildModelItems(ctx?: ExtensionContext): SelectItem[] {
|
|
265
|
+
const items: SelectItem[] = [
|
|
266
|
+
{
|
|
267
|
+
value: "disabled",
|
|
268
|
+
label: "disabled",
|
|
269
|
+
description: "No prompt suggestions",
|
|
270
|
+
},
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
if (!ctx) return items;
|
|
274
|
+
|
|
275
|
+
const models = getSelectableModels(ctx);
|
|
276
|
+
|
|
277
|
+
for (const model of models) {
|
|
278
|
+
const suffix = model.isCurrent ? " [current]" : "";
|
|
279
|
+
items.push({
|
|
280
|
+
value: model.canonicalId,
|
|
281
|
+
label: `${model.canonicalId}${suffix}`,
|
|
282
|
+
description: model.label !== model.canonicalId ? model.label : undefined,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return items;
|
|
287
|
+
}
|
|
288
|
+
|
|
185
289
|
// ── Entry point ──────────────────────────────────────────────
|
|
186
290
|
|
|
187
291
|
export function openSettingsOverlay(ctx: ExtensionContext): void {
|
|
@@ -196,6 +300,7 @@ export function openSettingsOverlay(ctx: ExtensionContext): void {
|
|
|
196
300
|
const container = new Container();
|
|
197
301
|
|
|
198
302
|
const deps: SettingsOverlayDeps = {
|
|
303
|
+
ctx,
|
|
199
304
|
state,
|
|
200
305
|
container,
|
|
201
306
|
settingsList: null,
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
// supi-core settings-ui domain — settings TUI components (imports pi-tui at runtime, heavy).
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
createInputSubmenu,
|
|
4
|
+
createModelPickerSubmenu,
|
|
5
|
+
openSettingsOverlay,
|
|
6
|
+
} from "./settings/settings-ui.ts";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared UI constants for SuPi extensions.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Braille spinner frames used across SuPi extensions for animated loaders. */
|
|
8
|
+
export const BRAILLE_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
9
|
+
|
|
10
|
+
/** Tick interval (ms) shared by all braille-spinner consumers. */
|
|
11
|
+
export const SPINNER_INTERVAL_MS = 80;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight status-bar spinner for SuPi extensions.
|
|
3
|
+
*
|
|
4
|
+
* Manages a setInterval-based animated spinner that writes to
|
|
5
|
+
* `ctx.ui.setStatus`. Each tick advances the frame and re-renders
|
|
6
|
+
* with the current message.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { BRAILLE_SPINNER_FRAMES, SPINNER_INTERVAL_MS } from "./spinner-frames.ts";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Manages an animated braille spinner on the status bar.
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* ```ts
|
|
19
|
+
* const spinner = new StatusSpinner(ctx, "my-package");
|
|
20
|
+
* spinner.start("generating…");
|
|
21
|
+
* // later
|
|
22
|
+
* spinner.stop();
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export class StatusSpinner {
|
|
26
|
+
private interval: ReturnType<typeof setInterval> | null = null;
|
|
27
|
+
private frame = 0;
|
|
28
|
+
private currentMessage = "";
|
|
29
|
+
|
|
30
|
+
constructor(
|
|
31
|
+
private ctx: ExtensionContext,
|
|
32
|
+
private source: string,
|
|
33
|
+
private frames: readonly string[] = BRAILLE_SPINNER_FRAMES,
|
|
34
|
+
) {}
|
|
35
|
+
|
|
36
|
+
/** Start the spinner with the given message. Overwrites any active spinner. */
|
|
37
|
+
start(message: string): void {
|
|
38
|
+
this.stop();
|
|
39
|
+
this.currentMessage = message;
|
|
40
|
+
this.render();
|
|
41
|
+
|
|
42
|
+
this.interval = setInterval(() => {
|
|
43
|
+
this.frame++;
|
|
44
|
+
this.render();
|
|
45
|
+
}, SPINNER_INTERVAL_MS);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Update the display message without resetting the spinner. */
|
|
49
|
+
update(message: string): void {
|
|
50
|
+
this.currentMessage = message;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Stop the spinner and clear the status. */
|
|
54
|
+
stop(): void {
|
|
55
|
+
if (this.interval !== null) {
|
|
56
|
+
clearInterval(this.interval);
|
|
57
|
+
this.interval = null;
|
|
58
|
+
}
|
|
59
|
+
this.ctx.ui.setStatus(this.source, "");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── Private ──────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
private render(): void {
|
|
65
|
+
const icon = this.frames[this.frame % this.frames.length];
|
|
66
|
+
this.ctx.ui.setStatus(this.source, `${icon} ${this.currentMessage}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-lsp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "SuPi LSP extension — Language Server Protocol integration for pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
|
-
"pi-package",
|
|
20
19
|
"pi",
|
|
21
20
|
"pi-coding-agent",
|
|
22
21
|
"pi-extension",
|
|
@@ -27,6 +26,7 @@
|
|
|
27
26
|
"rename",
|
|
28
27
|
"diagnostics"
|
|
29
28
|
],
|
|
29
|
+
"type": "module",
|
|
30
30
|
"files": [
|
|
31
31
|
"src/**/*.ts",
|
|
32
32
|
"src/**/*.json",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"vscode-jsonrpc": "^9.0.0",
|
|
39
39
|
"vscode-languageserver-protocol": "^3.17.5",
|
|
40
40
|
"vscode-languageserver-types": "^3.17.5",
|
|
41
|
-
"@mrclrchtr/supi-code-runtime": "
|
|
42
|
-
"@mrclrchtr/supi-core": "
|
|
41
|
+
"@mrclrchtr/supi-code-runtime": "2.0.1",
|
|
42
|
+
"@mrclrchtr/supi-core": "2.0.1"
|
|
43
43
|
},
|
|
44
44
|
"bundledDependencies": [
|
|
45
45
|
"@mrclrchtr/supi-code-runtime",
|
|
@@ -50,36 +50,9 @@
|
|
|
50
50
|
"vscode-languageserver-protocol",
|
|
51
51
|
"vscode-languageserver-types"
|
|
52
52
|
],
|
|
53
|
-
"peerDependencies": {
|
|
54
|
-
"@earendil-works/pi-ai": "*",
|
|
55
|
-
"@earendil-works/pi-coding-agent": "*",
|
|
56
|
-
"@earendil-works/pi-tui": "*",
|
|
57
|
-
"typebox": "*"
|
|
58
|
-
},
|
|
59
|
-
"peerDependenciesMeta": {
|
|
60
|
-
"@earendil-works/pi-ai": {
|
|
61
|
-
"optional": true
|
|
62
|
-
},
|
|
63
|
-
"@earendil-works/pi-coding-agent": {
|
|
64
|
-
"optional": true
|
|
65
|
-
},
|
|
66
|
-
"@earendil-works/pi-tui": {
|
|
67
|
-
"optional": true
|
|
68
|
-
},
|
|
69
|
-
"typebox": {
|
|
70
|
-
"optional": true
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
"pi": {
|
|
74
|
-
"extensions": [
|
|
75
|
-
"./src/extension.ts"
|
|
76
|
-
],
|
|
77
|
-
"image": "https://raw.githubusercontent.com/mrclrchtr/supi/main/screenshots/supi-lsp-status.png"
|
|
78
|
-
},
|
|
79
53
|
"main": "src/api.ts",
|
|
80
54
|
"exports": {
|
|
81
55
|
"./api": "./src/api.ts",
|
|
82
|
-
"./extension": "./src/extension.ts",
|
|
83
56
|
"./package.json": "./package.json",
|
|
84
57
|
"./provider/lsp-semantic-provider": "./src/provider/lsp-semantic-provider.ts"
|
|
85
58
|
}
|
package/src/api.ts
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
// Public API surface for the LSP session-scoped service.
|
|
2
2
|
|
|
3
|
+
export { type LoadConfigOptions, loadConfig } from "./config/config.ts";
|
|
4
|
+
export type { DeprecatedLspKeys, LspSettings } from "./config/lsp-settings.ts";
|
|
5
|
+
export {
|
|
6
|
+
getDeprecatedLspKeys,
|
|
7
|
+
getLspDisabledMessage,
|
|
8
|
+
hasDeprecatedLspKeys,
|
|
9
|
+
loadLspSettings,
|
|
10
|
+
} from "./config/lsp-settings.ts";
|
|
11
|
+
export { clearTsconfigCache } from "./config/tsconfig-scope.ts";
|
|
3
12
|
export type {
|
|
4
13
|
CodeAction,
|
|
5
14
|
Diagnostic,
|
|
6
15
|
DocumentSymbol,
|
|
16
|
+
FileEvent,
|
|
7
17
|
Hover,
|
|
8
18
|
Location,
|
|
9
19
|
LocationLink,
|
|
20
|
+
LspConfig,
|
|
21
|
+
MissingServer,
|
|
10
22
|
Position,
|
|
11
23
|
ProjectServerInfo,
|
|
12
24
|
Range,
|
|
@@ -14,15 +26,30 @@ export type {
|
|
|
14
26
|
WorkspaceEdit,
|
|
15
27
|
WorkspaceSymbol,
|
|
16
28
|
} from "./config/types.ts";
|
|
29
|
+
export { FileChangeType } from "./config/types.ts";
|
|
17
30
|
export { toLspPosition, toOneBasedPosition } from "./coordinates.ts";
|
|
31
|
+
export { isLikelyStaleDiagnostic } from "./diagnostics/stale-diagnostics.ts";
|
|
32
|
+
export {
|
|
33
|
+
scanWorkspaceSentinels,
|
|
34
|
+
syncWorkspaceSentinelSnapshot,
|
|
35
|
+
} from "./diagnostics/workspace-sentinels.ts";
|
|
36
|
+
export type {
|
|
37
|
+
LspControllerState,
|
|
38
|
+
LspStartResult,
|
|
39
|
+
} from "./session/runtime-controller.ts";
|
|
40
|
+
export { LspRuntimeController } from "./session/runtime-controller.ts";
|
|
41
|
+
export { scanMissingServers } from "./session/scanner.ts";
|
|
18
42
|
export type {
|
|
19
43
|
OutstandingDiagnosticSummaryEntry,
|
|
20
44
|
RecoverDiagnosticsResult,
|
|
45
|
+
SemanticReadinessResult,
|
|
21
46
|
SessionLspServiceState,
|
|
22
47
|
WorkspaceDiagnosticSummaryEntry,
|
|
23
48
|
} from "./session/service-registry.ts";
|
|
24
49
|
export {
|
|
50
|
+
clearSessionLspService,
|
|
25
51
|
getSessionLspService,
|
|
26
52
|
SessionLspService,
|
|
53
|
+
setSessionLspServiceState,
|
|
27
54
|
waitForSessionLspService,
|
|
28
55
|
} from "./session/service-registry.ts";
|
|
@@ -147,6 +147,11 @@ async function pullDocumentDiagnostics(
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
const previousResultId = accessClient(client).diagnosticStore.get(uri)?.resultId;
|
|
150
|
+
// Wait for server readiness before pulling diagnostics. In steady state
|
|
151
|
+
// this resolves immediately (isReady check). During active indexing, this
|
|
152
|
+
// may add extra latency on top of the per-URI timeout budget — acceptable
|
|
153
|
+
// because the server wouldn't return useful diagnostics while indexing anyway.
|
|
154
|
+
await client.getReady();
|
|
150
155
|
return rpc.sendRequest(
|
|
151
156
|
"textDocument/diagnostic",
|
|
152
157
|
{
|