3e237e177a
* feat: add code-walkthrough backend (MCP tool, service, persistence, WS events) * feat: render agent code walkthroughs as line-anchored cards + editor window * test: add code-walkthrough re-emit mock-agent scenario * fix: rework code-walkthrough UX (floating card, line reveal, minimize, clickable file) * fix: improve code walkthrough editor UX * fix: backfill walkthrough launcher after websocket connect * fix: improve walkthrough requests and editor anchors * fix: clear walkthrough highlights on close * feat: add walkthrough discard and prompt customization * fix: send walkthrough requests through saved prompt * fix: support postgres built-in prompt seeding * fix: address walkthrough review feedback * fix: address walkthrough review follow-ups * fix: harden walkthrough lifecycle races * fix: reuse walkthrough notification cleanup * fix: guard stale walkthrough file opens * fix: abort stale file opens before paint * fix: stabilize github settings e2e readiness * fix: use stable github settings e2e selector
78 lines
2.6 KiB
TypeScript
78 lines
2.6 KiB
TypeScript
import { getMonacoInstance } from "@/components/editors/monaco/monaco-init";
|
|
import { walkthroughFileMatches } from "@/lib/diff/walkthrough-match";
|
|
import { buildRepoScopedItemId } from "@/lib/state/dockview-panel-actions";
|
|
|
|
const pendingCursorPositions = new Map<string, { line: number; column: number }>();
|
|
|
|
function pendingCursorKey(path: string, repo?: string): string {
|
|
return buildRepoScopedItemId(path, repo);
|
|
}
|
|
|
|
export function setPendingCursorPosition(
|
|
path: string,
|
|
line: number,
|
|
column: number,
|
|
repo?: string,
|
|
) {
|
|
pendingCursorPositions.set(pendingCursorKey(path, repo), { line, column });
|
|
}
|
|
|
|
export function consumePendingCursorPosition(
|
|
path: string,
|
|
repo?: string,
|
|
): { line: number; column: number } | undefined {
|
|
const key = pendingCursorKey(path, repo);
|
|
const pos = pendingCursorPositions.get(key);
|
|
if (pos) pendingCursorPositions.delete(key);
|
|
return pos;
|
|
}
|
|
|
|
function pathSegments(path: string): string[] {
|
|
return path.trim().replaceAll("\\", "/").split("/").filter(Boolean);
|
|
}
|
|
|
|
function repoScopedModelMatches(modelPath: string, repo: string | undefined, path: string) {
|
|
const repoSegments = pathSegments(repo ?? "");
|
|
if (repoSegments.length === 0) return false;
|
|
const modelSegments = pathSegments(modelPath);
|
|
const targetSegments = [...repoSegments, ...pathSegments(path)];
|
|
if (targetSegments.length > modelSegments.length) return false;
|
|
const offset = modelSegments.length - targetSegments.length;
|
|
return targetSegments.every((segment, index) => modelSegments[offset + index] === segment);
|
|
}
|
|
|
|
function editorModelMatches(modelPath: string, monacoPath: string, path: string, repo?: string) {
|
|
const exactMatch = modelPath === `/${monacoPath}` || modelPath === monacoPath;
|
|
if (repo) return repoScopedModelMatches(modelPath, repo, path);
|
|
return exactMatch || walkthroughFileMatches(modelPath, path);
|
|
}
|
|
|
|
export function scrollEditorIfMounted(
|
|
path: string,
|
|
worktreePath: string | null,
|
|
line: number,
|
|
column: number,
|
|
repo?: string,
|
|
): boolean {
|
|
const monaco = getMonacoInstance();
|
|
if (!monaco) return false;
|
|
|
|
const monacoPath = worktreePath ? `${worktreePath}/${path}` : path;
|
|
for (const editor of monaco.editor.getEditors()) {
|
|
const model = editor.getModel();
|
|
if (!model) continue;
|
|
const modelPath = model.uri.path;
|
|
const matches = worktreePath
|
|
? modelPath === `/${monacoPath}` || modelPath === monacoPath
|
|
: editorModelMatches(modelPath, monacoPath, path, repo);
|
|
if (matches) {
|
|
consumePendingCursorPosition(path, repo);
|
|
editor.setPosition({ lineNumber: line, column });
|
|
editor.revealLineInCenter(line);
|
|
editor.focus();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|