@oh-my-pi/pi-coding-agent 10.2.1 → 10.2.3
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/CHANGELOG.md +45 -0
- package/package.json +7 -7
- package/src/commit/agentic/prompts/analyze-file.md +7 -7
- package/src/commit/agentic/prompts/session-user.md +4 -4
- package/src/commit/agentic/prompts/system.md +14 -16
- package/src/commit/prompts/analysis-system.md +7 -9
- package/src/commit/prompts/analysis-user.md +0 -3
- package/src/commit/prompts/changelog-system.md +14 -19
- package/src/commit/prompts/file-observer-system.md +2 -2
- package/src/commit/prompts/reduce-system.md +13 -23
- package/src/commit/prompts/summary-system.md +7 -21
- package/src/config/settings-schema.ts +135 -38
- package/src/cursor.ts +2 -1
- package/src/debug/index.ts +1 -1
- package/src/debug/report-bundle.ts +1 -1
- package/src/extensibility/extensions/index.ts +0 -11
- package/src/extensibility/extensions/types.ts +1 -30
- package/src/extensibility/hooks/types.ts +1 -31
- package/src/index.ts +0 -11
- package/src/ipy/prelude.py +1 -113
- package/src/lsp/index.ts +66 -515
- package/src/lsp/render.ts +0 -11
- package/src/lsp/types.ts +3 -87
- package/src/modes/components/settings-defs.ts +3 -2
- package/src/modes/components/settings-selector.ts +14 -14
- package/src/modes/interactive-mode.ts +5 -5
- package/src/modes/theme/theme.ts +45 -1
- package/src/prompts/agents/designer.md +23 -27
- package/src/prompts/agents/explore.md +28 -38
- package/src/prompts/agents/init.md +17 -17
- package/src/prompts/agents/plan.md +21 -27
- package/src/prompts/agents/reviewer.md +37 -37
- package/src/prompts/compaction/branch-summary.md +9 -9
- package/src/prompts/compaction/compaction-summary.md +8 -12
- package/src/prompts/compaction/compaction-update-summary.md +17 -19
- package/src/prompts/review-request.md +12 -13
- package/src/prompts/system/custom-system-prompt.md +6 -26
- package/src/prompts/system/plan-mode-active.md +23 -35
- package/src/prompts/system/plan-mode-subagent.md +7 -7
- package/src/prompts/system/subagent-system-prompt.md +7 -7
- package/src/prompts/system/system-prompt.md +84 -125
- package/src/prompts/system/web-search.md +10 -10
- package/src/prompts/tools/ask.md +12 -15
- package/src/prompts/tools/bash.md +7 -7
- package/src/prompts/tools/exit-plan-mode.md +6 -6
- package/src/prompts/tools/gemini-image.md +4 -4
- package/src/prompts/tools/grep.md +4 -4
- package/src/prompts/tools/lsp.md +12 -19
- package/src/prompts/tools/patch.md +26 -30
- package/src/prompts/tools/python.md +14 -57
- package/src/prompts/tools/read.md +4 -4
- package/src/prompts/tools/replace.md +8 -8
- package/src/prompts/tools/ssh.md +14 -27
- package/src/prompts/tools/task.md +23 -35
- package/src/prompts/tools/todo-write.md +29 -38
- package/src/prompts/tools/write.md +3 -3
- package/src/sdk.ts +0 -2
- package/src/session/agent-session.ts +27 -6
- package/src/system-prompt.ts +1 -219
- package/src/task/agents.ts +2 -1
- package/src/tools/bash-interceptor.ts +0 -24
- package/src/tools/bash.ts +1 -7
- package/src/tools/index.ts +8 -3
- package/src/tools/read.ts +74 -17
- package/src/tools/renderers.ts +0 -2
- package/src/lsp/rust-analyzer.ts +0 -184
- package/src/tools/ls.ts +0 -307
package/src/tools/bash.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { Theme } from "../modes/theme/theme";
|
|
|
11
11
|
import bashDescription from "../prompts/tools/bash.md" with { type: "text" };
|
|
12
12
|
import { renderOutputBlock, renderStatusLine } from "../tui";
|
|
13
13
|
import type { ToolSession } from ".";
|
|
14
|
-
import { checkBashInterception
|
|
14
|
+
import { checkBashInterception } from "./bash-interceptor";
|
|
15
15
|
import { applyHeadTail, normalizeBashCommand } from "./bash-normalize";
|
|
16
16
|
import type { OutputMeta } from "./output-meta";
|
|
17
17
|
import { allocateOutputArtifact, createTailBuffer } from "./output-utils";
|
|
@@ -83,12 +83,6 @@ export class BashTool implements AgentTool<typeof bashSchema, BashToolDetails> {
|
|
|
83
83
|
if (interception.block) {
|
|
84
84
|
throw new ToolError(interception.message ?? "Command blocked");
|
|
85
85
|
}
|
|
86
|
-
if (this.session.settings.get("bashInterceptor.simpleLs")) {
|
|
87
|
-
const lsInterception = checkSimpleLsInterception(command, ctx?.toolNames ?? []);
|
|
88
|
-
if (lsInterception.block) {
|
|
89
|
-
throw new ToolError(lsInterception.message ?? "Command blocked");
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
86
|
}
|
|
93
87
|
|
|
94
88
|
const commandCwd = cwd ? resolveToCwd(cwd, this.session.cwd) : this.session.cwd;
|
package/src/tools/index.ts
CHANGED
|
@@ -22,7 +22,6 @@ import { ExitPlanModeTool } from "./exit-plan-mode";
|
|
|
22
22
|
import { FetchTool } from "./fetch";
|
|
23
23
|
import { FindTool } from "./find";
|
|
24
24
|
import { GrepTool } from "./grep";
|
|
25
|
-
import { LsTool } from "./ls";
|
|
26
25
|
import { NotebookTool } from "./notebook";
|
|
27
26
|
import { wrapToolsWithMetaNotice } from "./output-meta";
|
|
28
27
|
import { PythonTool } from "./python";
|
|
@@ -76,7 +75,6 @@ export { FetchTool, type FetchToolDetails } from "./fetch";
|
|
|
76
75
|
export { type FindOperations, FindTool, type FindToolDetails, type FindToolOptions } from "./find";
|
|
77
76
|
export { setPreferredImageProvider } from "./gemini-image";
|
|
78
77
|
export { type GrepOperations, GrepTool, type GrepToolDetails, type GrepToolOptions } from "./grep";
|
|
79
|
-
export { type LsOperations, LsTool, type LsToolDetails, type LsToolOptions } from "./ls";
|
|
80
78
|
export { NotebookTool, type NotebookToolDetails } from "./notebook";
|
|
81
79
|
export { PythonTool, type PythonToolDetails, type PythonToolOptions } from "./python";
|
|
82
80
|
export { ReadTool, type ReadToolDetails } from "./read";
|
|
@@ -170,7 +168,6 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
170
168
|
edit: s => new EditTool(s),
|
|
171
169
|
find: s => new FindTool(s),
|
|
172
170
|
grep: s => new GrepTool(s),
|
|
173
|
-
ls: s => new LsTool(s),
|
|
174
171
|
lsp: LspTool.createIf,
|
|
175
172
|
notebook: s => new NotebookTool(s),
|
|
176
173
|
read: s => new ReadTool(s),
|
|
@@ -278,6 +275,14 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
278
275
|
if (name === "lsp") return enableLsp;
|
|
279
276
|
if (name === "bash") return allowBash;
|
|
280
277
|
if (name === "python") return allowPython;
|
|
278
|
+
if (name === "todo_write") return !includeSubmitResult && session.settings.get("todo.enabled");
|
|
279
|
+
if (name === "find") return session.settings.get("find.enabled");
|
|
280
|
+
if (name === "grep") return session.settings.get("grep.enabled");
|
|
281
|
+
if (name === "notebook") return session.settings.get("notebook.enabled");
|
|
282
|
+
if (name === "fetch") return session.settings.get("fetch.enabled");
|
|
283
|
+
if (name === "web_search") return session.settings.get("web_search.enabled");
|
|
284
|
+
if (name === "lsp") return session.settings.get("lsp.enabled");
|
|
285
|
+
if (name === "calc") return session.settings.get("calc.enabled");
|
|
281
286
|
return true;
|
|
282
287
|
};
|
|
283
288
|
if (includeSubmitResult && requestedTools && !requestedTools.includes("submit_result")) {
|
package/src/tools/read.ts
CHANGED
|
@@ -19,10 +19,9 @@ import { formatDimensionNote, resizeImage } from "../utils/image-resize";
|
|
|
19
19
|
import { detectSupportedImageMimeTypeFromFile } from "../utils/mime";
|
|
20
20
|
import { ensureTool } from "../utils/tools-manager";
|
|
21
21
|
import { applyListLimit } from "./list-limit";
|
|
22
|
-
import { LsTool } from "./ls";
|
|
23
22
|
import type { OutputMeta } from "./output-meta";
|
|
24
23
|
import { resolveReadPath, resolveToCwd } from "./path-utils";
|
|
25
|
-
import { shortenPath, wrapBrackets } from "./render-utils";
|
|
24
|
+
import { formatAge, shortenPath, wrapBrackets } from "./render-utils";
|
|
26
25
|
import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
|
|
27
26
|
import { toolResult } from "./tool-result";
|
|
28
27
|
import {
|
|
@@ -519,7 +518,7 @@ const readSchema = Type.Object({
|
|
|
519
518
|
|
|
520
519
|
export interface ReadToolDetails {
|
|
521
520
|
truncation?: TruncationResult;
|
|
522
|
-
|
|
521
|
+
isDirectory?: boolean;
|
|
523
522
|
resolvedPath?: string;
|
|
524
523
|
meta?: OutputMeta;
|
|
525
524
|
}
|
|
@@ -530,7 +529,7 @@ type ReadParams = { path: string; offset?: number; limit?: number; lines?: boole
|
|
|
530
529
|
* Read tool implementation.
|
|
531
530
|
*
|
|
532
531
|
* Reads files with support for images, documents (via markitdown), and text.
|
|
533
|
-
* Directories
|
|
532
|
+
* Directories return a formatted listing with modification times.
|
|
534
533
|
*/
|
|
535
534
|
export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
536
535
|
public readonly name = "read";
|
|
@@ -542,20 +541,18 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
542
541
|
private readonly session: ToolSession;
|
|
543
542
|
private readonly autoResizeImages: boolean;
|
|
544
543
|
private readonly defaultLineNumbers: boolean;
|
|
545
|
-
private readonly lsTool: LsTool;
|
|
546
544
|
|
|
547
545
|
constructor(session: ToolSession) {
|
|
548
546
|
this.session = session;
|
|
549
547
|
this.autoResizeImages = session.settings.get("images.autoResize");
|
|
550
548
|
this.defaultLineNumbers = session.settings.get("readLineNumbers");
|
|
551
|
-
this.lsTool = new LsTool(session);
|
|
552
549
|
this.description = renderPromptTemplate(readDescription, {
|
|
553
550
|
DEFAULT_MAX_LINES: String(DEFAULT_MAX_LINES),
|
|
554
551
|
});
|
|
555
552
|
}
|
|
556
553
|
|
|
557
554
|
public async execute(
|
|
558
|
-
|
|
555
|
+
_toolCallId: string,
|
|
559
556
|
params: ReadParams,
|
|
560
557
|
signal?: AbortSignal,
|
|
561
558
|
_onUpdate?: AgentToolUpdateCallback<ReadToolDetails>,
|
|
@@ -606,13 +603,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
606
603
|
}
|
|
607
604
|
|
|
608
605
|
if (isDirectory) {
|
|
609
|
-
|
|
610
|
-
const details: ReadToolDetails = {
|
|
611
|
-
redirectedTo: "ls",
|
|
612
|
-
truncation: lsResult.details?.truncation,
|
|
613
|
-
meta: lsResult.details?.meta,
|
|
614
|
-
};
|
|
615
|
-
return toolResult(details).content(lsResult.content).done();
|
|
606
|
+
return this.readDirectory(absolutePath, limit, signal);
|
|
616
607
|
}
|
|
617
608
|
|
|
618
609
|
const mimeType = await detectSupportedImageMimeTypeFromFile(absolutePath);
|
|
@@ -982,6 +973,75 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
982
973
|
}
|
|
983
974
|
return resultBuilder.done();
|
|
984
975
|
}
|
|
976
|
+
|
|
977
|
+
/** Read directory contents as a formatted listing */
|
|
978
|
+
private async readDirectory(
|
|
979
|
+
absolutePath: string,
|
|
980
|
+
limit: number | undefined,
|
|
981
|
+
signal?: AbortSignal,
|
|
982
|
+
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
983
|
+
const DEFAULT_LIMIT = 500;
|
|
984
|
+
const effectiveLimit = limit ?? DEFAULT_LIMIT;
|
|
985
|
+
|
|
986
|
+
let entries: string[];
|
|
987
|
+
try {
|
|
988
|
+
entries = await fs.readdir(absolutePath);
|
|
989
|
+
} catch (error) {
|
|
990
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
991
|
+
throw new ToolError(`Cannot read directory: ${message}`);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Sort alphabetically (case-insensitive)
|
|
995
|
+
entries.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
996
|
+
|
|
997
|
+
const listLimit = applyListLimit(entries, { limit: effectiveLimit });
|
|
998
|
+
const limitedEntries = listLimit.items;
|
|
999
|
+
const limitMeta = listLimit.meta;
|
|
1000
|
+
|
|
1001
|
+
// Format entries with directory indicators and ages
|
|
1002
|
+
const results: string[] = [];
|
|
1003
|
+
|
|
1004
|
+
for (const entry of limitedEntries) {
|
|
1005
|
+
throwIfAborted(signal);
|
|
1006
|
+
const fullPath = path.join(absolutePath, entry);
|
|
1007
|
+
let suffix = "";
|
|
1008
|
+
let age = "";
|
|
1009
|
+
|
|
1010
|
+
try {
|
|
1011
|
+
const entryStat = await fs.stat(fullPath);
|
|
1012
|
+
suffix = entryStat.isDirectory() ? "/" : "";
|
|
1013
|
+
const ageSeconds = Math.floor((Date.now() - entryStat.mtimeMs) / 1000);
|
|
1014
|
+
age = formatAge(ageSeconds);
|
|
1015
|
+
} catch {
|
|
1016
|
+
// Skip entries we can't stat
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const line = age ? `${entry}${suffix} (${age})` : entry + suffix;
|
|
1021
|
+
results.push(line);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if (results.length === 0) {
|
|
1025
|
+
return { content: [{ type: "text", text: "(empty directory)" }], details: {} };
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
const output = results.join("\n");
|
|
1029
|
+
const truncation = truncateHead(output, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
1030
|
+
|
|
1031
|
+
const details: ReadToolDetails = {
|
|
1032
|
+
isDirectory: true,
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
const resultBuilder = toolResult(details)
|
|
1036
|
+
.text(truncation.content)
|
|
1037
|
+
.limits({ resultLimit: limitMeta.resultLimit?.reached });
|
|
1038
|
+
if (truncation.truncated) {
|
|
1039
|
+
resultBuilder.truncation(truncation, { direction: "head" });
|
|
1040
|
+
details.truncation = truncation;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
return resultBuilder.done();
|
|
1044
|
+
}
|
|
985
1045
|
}
|
|
986
1046
|
|
|
987
1047
|
// =============================================================================
|
|
@@ -1029,9 +1089,6 @@ export const readToolRenderer = {
|
|
|
1029
1089
|
const warningLines: string[] = [];
|
|
1030
1090
|
const truncation = details?.meta?.truncation;
|
|
1031
1091
|
const fallback = details?.truncation;
|
|
1032
|
-
if (details?.redirectedTo) {
|
|
1033
|
-
warningLines.push(uiTheme.fg("warning", wrapBrackets(`Redirected to ${details.redirectedTo}`, uiTheme)));
|
|
1034
|
-
}
|
|
1035
1092
|
if (details?.resolvedPath) {
|
|
1036
1093
|
warningLines.push(uiTheme.fg("dim", wrapBrackets(`Resolved path: ${details.resolvedPath}`, uiTheme)));
|
|
1037
1094
|
}
|
package/src/tools/renderers.ts
CHANGED
|
@@ -16,7 +16,6 @@ import { calculatorToolRenderer } from "./calculator";
|
|
|
16
16
|
import { fetchToolRenderer } from "./fetch";
|
|
17
17
|
import { findToolRenderer } from "./find";
|
|
18
18
|
import { grepToolRenderer } from "./grep";
|
|
19
|
-
import { lsToolRenderer } from "./ls";
|
|
20
19
|
import { notebookToolRenderer } from "./notebook";
|
|
21
20
|
import { pythonToolRenderer } from "./python";
|
|
22
21
|
import { readToolRenderer } from "./read";
|
|
@@ -49,7 +48,6 @@ export const toolRenderers: Record<string, ToolRenderer> = {
|
|
|
49
48
|
edit: editToolRenderer as ToolRenderer,
|
|
50
49
|
find: findToolRenderer as ToolRenderer,
|
|
51
50
|
grep: grepToolRenderer as ToolRenderer,
|
|
52
|
-
ls: lsToolRenderer as ToolRenderer,
|
|
53
51
|
lsp: lspToolRenderer as ToolRenderer,
|
|
54
52
|
notebook: notebookToolRenderer as ToolRenderer,
|
|
55
53
|
read: readToolRenderer as ToolRenderer,
|
package/src/lsp/rust-analyzer.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { sendNotification, sendRequest } from "./client";
|
|
2
|
-
import type { Diagnostic, ExpandMacroResult, LspClient, RelatedTest, Runnable, WorkspaceEdit } from "./types";
|
|
3
|
-
import { fileToUri } from "./utils";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Run flycheck (cargo check) and collect diagnostics.
|
|
7
|
-
* Sends rust-analyzer/runFlycheck notification and waits for diagnostics to accumulate.
|
|
8
|
-
*
|
|
9
|
-
* @param client - LSP client instance
|
|
10
|
-
* @param file - Optional file path to check (if not provided, checks entire workspace)
|
|
11
|
-
* @returns Array of all collected diagnostics
|
|
12
|
-
*/
|
|
13
|
-
export async function flycheck(client: LspClient, file?: string): Promise<Diagnostic[]> {
|
|
14
|
-
const textDocument = file ? { uri: fileToUri(file) } : null;
|
|
15
|
-
|
|
16
|
-
const countDiagnostics = (diagnostics: Map<string, Diagnostic[]>): number => {
|
|
17
|
-
let count = 0;
|
|
18
|
-
for (const diags of diagnostics.values()) {
|
|
19
|
-
count += diags.length;
|
|
20
|
-
}
|
|
21
|
-
return count;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
// Capture current diagnostic version before triggering flycheck
|
|
25
|
-
const initialDiagnosticsVersion = client.diagnosticsVersion;
|
|
26
|
-
const initialDiagnosticsCount = countDiagnostics(client.diagnostics);
|
|
27
|
-
|
|
28
|
-
await sendNotification(client, "rust-analyzer/runFlycheck", { textDocument });
|
|
29
|
-
|
|
30
|
-
// Bounded polling: wait for diagnostics to stabilize or timeout
|
|
31
|
-
// Poll every 100ms for up to 8 seconds (80 iterations)
|
|
32
|
-
const pollIntervalMs = 100;
|
|
33
|
-
const maxPollIterations = 80;
|
|
34
|
-
const stabilityThreshold = 3; // Consider stable after 3 iterations without change
|
|
35
|
-
const minStableDurationMs = 2000; // Avoid early exit when diagnostics are re-published unchanged.
|
|
36
|
-
const startTime = Date.now();
|
|
37
|
-
let lastDiagnosticsVersion = initialDiagnosticsVersion;
|
|
38
|
-
let lastDiagnosticsCount = initialDiagnosticsCount;
|
|
39
|
-
let stableIterations = 0;
|
|
40
|
-
|
|
41
|
-
for (let i = 0; i < maxPollIterations; i++) {
|
|
42
|
-
await Bun.sleep(pollIntervalMs);
|
|
43
|
-
|
|
44
|
-
const currentDiagnosticsVersion = client.diagnosticsVersion;
|
|
45
|
-
const currentDiagnosticsCount = countDiagnostics(client.diagnostics);
|
|
46
|
-
|
|
47
|
-
// Check if diagnostics have stabilized
|
|
48
|
-
if (currentDiagnosticsVersion === lastDiagnosticsVersion && currentDiagnosticsCount === lastDiagnosticsCount) {
|
|
49
|
-
stableIterations++;
|
|
50
|
-
const elapsedMs = Date.now() - startTime;
|
|
51
|
-
const countChangedFromStart = currentDiagnosticsCount !== initialDiagnosticsCount;
|
|
52
|
-
if (
|
|
53
|
-
currentDiagnosticsVersion !== initialDiagnosticsVersion &&
|
|
54
|
-
stableIterations >= stabilityThreshold &&
|
|
55
|
-
(countChangedFromStart || elapsedMs >= minStableDurationMs)
|
|
56
|
-
) {
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
} else {
|
|
60
|
-
stableIterations = 0;
|
|
61
|
-
lastDiagnosticsVersion = currentDiagnosticsVersion;
|
|
62
|
-
lastDiagnosticsCount = currentDiagnosticsCount;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Collect all diagnostics from client
|
|
67
|
-
const allDiags: Diagnostic[] = [];
|
|
68
|
-
for (const diags of Array.from(client.diagnostics.values())) {
|
|
69
|
-
allDiags.push(...diags);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return allDiags;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Expand macro at the given position.
|
|
77
|
-
*
|
|
78
|
-
* @param client - LSP client instance
|
|
79
|
-
* @param file - File path containing the macro
|
|
80
|
-
* @param line - 1-based line number
|
|
81
|
-
* @param character - 1-based character offset
|
|
82
|
-
* @returns ExpandMacroResult with macro name and expansion, or null if no macro at position
|
|
83
|
-
*/
|
|
84
|
-
export async function expandMacro(
|
|
85
|
-
client: LspClient,
|
|
86
|
-
file: string,
|
|
87
|
-
line: number,
|
|
88
|
-
character: number,
|
|
89
|
-
): Promise<ExpandMacroResult | null> {
|
|
90
|
-
const result = (await sendRequest(client, "rust-analyzer/expandMacro", {
|
|
91
|
-
textDocument: { uri: fileToUri(file) },
|
|
92
|
-
position: { line: line - 1, character: character - 1 },
|
|
93
|
-
})) as ExpandMacroResult | null;
|
|
94
|
-
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Perform structural search and replace (SSR).
|
|
100
|
-
*
|
|
101
|
-
* @param client - LSP client instance
|
|
102
|
-
* @param pattern - Search pattern
|
|
103
|
-
* @param replacement - Replacement pattern
|
|
104
|
-
* @param parseOnly - If true, returns matches only; if false, returns WorkspaceEdit to apply
|
|
105
|
-
* @returns WorkspaceEdit containing matches or changes to apply
|
|
106
|
-
*/
|
|
107
|
-
export async function ssr(
|
|
108
|
-
client: LspClient,
|
|
109
|
-
pattern: string,
|
|
110
|
-
replacement: string,
|
|
111
|
-
parseOnly = true,
|
|
112
|
-
): Promise<WorkspaceEdit> {
|
|
113
|
-
const result = (await sendRequest(client, "experimental/ssr", {
|
|
114
|
-
query: `${pattern} ==>> ${replacement}`,
|
|
115
|
-
parseOnly,
|
|
116
|
-
textDocument: { uri: "" }, // SSR searches workspace-wide
|
|
117
|
-
position: { line: 0, character: 0 },
|
|
118
|
-
selections: [],
|
|
119
|
-
})) as WorkspaceEdit;
|
|
120
|
-
|
|
121
|
-
return result;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Get runnables (tests, binaries, examples) for a file.
|
|
126
|
-
*
|
|
127
|
-
* @param client - LSP client instance
|
|
128
|
-
* @param file - File path to query
|
|
129
|
-
* @param line - Optional 1-based line number to get runnables at specific position
|
|
130
|
-
* @returns Array of Runnable items
|
|
131
|
-
*/
|
|
132
|
-
export async function runnables(client: LspClient, file: string, line?: number): Promise<Runnable[]> {
|
|
133
|
-
const params: { textDocument: { uri: string }; position?: { line: number; character: number } } = {
|
|
134
|
-
textDocument: { uri: fileToUri(file) },
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
if (line !== undefined) {
|
|
138
|
-
params.position = { line: line - 1, character: 0 };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const result = (await sendRequest(client, "experimental/runnables", params)) as Runnable[];
|
|
142
|
-
return result ?? [];
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Get related tests for a position (e.g., tests for a function).
|
|
147
|
-
*
|
|
148
|
-
* @param client - LSP client instance
|
|
149
|
-
* @param file - File path
|
|
150
|
-
* @param line - 1-based line number
|
|
151
|
-
* @param character - 1-based character offset
|
|
152
|
-
* @returns Array of test runnable labels
|
|
153
|
-
*/
|
|
154
|
-
export async function relatedTests(
|
|
155
|
-
client: LspClient,
|
|
156
|
-
file: string,
|
|
157
|
-
line: number,
|
|
158
|
-
character: number,
|
|
159
|
-
): Promise<string[]> {
|
|
160
|
-
const tests = (await sendRequest(client, "rust-analyzer/relatedTests", {
|
|
161
|
-
textDocument: { uri: fileToUri(file) },
|
|
162
|
-
position: { line: line - 1, character: character - 1 },
|
|
163
|
-
})) as RelatedTest[];
|
|
164
|
-
|
|
165
|
-
if (!tests?.length) return [];
|
|
166
|
-
|
|
167
|
-
const labels: string[] = [];
|
|
168
|
-
for (const t of tests) {
|
|
169
|
-
if (t.runnable?.label) {
|
|
170
|
-
labels.push(t.runnable.label);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return labels;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Reload workspace (re-index Cargo projects).
|
|
179
|
-
*
|
|
180
|
-
* @param client - LSP client instance
|
|
181
|
-
*/
|
|
182
|
-
export async function reloadWorkspace(client: LspClient): Promise<void> {
|
|
183
|
-
await sendRequest(client, "rust-analyzer/reloadWorkspace", null);
|
|
184
|
-
}
|