Merge branch 'main' into keyword-nav-plan

# Conflicts:
#	packages/app/src/components/agent-input-area.tsx
#	packages/app/src/components/command-center.tsx
#	packages/app/src/hooks/use-global-keyboard-nav.ts
This commit is contained in:
Mohamed Boudra
2026-02-04 13:01:17 +07:00
15 changed files with 1012 additions and 1084 deletions
@@ -0,0 +1,33 @@
import { describe, expect, it } from "vitest";
import { parseInlinePathToken } from "./inline-path";
describe("parseInlinePathToken", () => {
it("returns null for plain paths (no line)", () => {
expect(parseInlinePathToken("src/app.ts")).toBeNull();
expect(parseInlinePathToken("README.md")).toBeNull();
});
it("parses filename:line", () => {
expect(parseInlinePathToken("src/app.ts:12")).toEqual({
raw: "src/app.ts:12",
path: "src/app.ts",
lineStart: 12,
lineEnd: undefined,
});
});
it("parses filename:lineStart-lineEnd", () => {
expect(parseInlinePathToken("src/app.ts:12-20")).toEqual({
raw: "src/app.ts:12-20",
path: "src/app.ts",
lineStart: 12,
lineEnd: 20,
});
});
it("rejects range-only :line tokens", () => {
expect(parseInlinePathToken(":12")).toBeNull();
expect(parseInlinePathToken(":12-20")).toBeNull();
});
});
+81
View File
@@ -0,0 +1,81 @@
export interface InlinePathTarget {
raw: string;
path: string;
lineStart?: number;
lineEnd?: number;
}
function normalizePathToken(value: string): string | null {
const trimmed = value
.trim()
.replace(/^['"`]/, "")
.replace(/['"`]$/, "");
if (!trimmed) {
return null;
}
return trimmed.replace(/\\/g, "/");
}
/**
* Strict VSCode-style markers only.
*
* Supported:
* - `filename:linenumber`
* - `filename:lineStart-lineEnd`
*
* Not supported (by design):
* - plain `filename` (no line)
* - `:linenumber` (range-only)
*/
export function parseInlinePathToken(value: string): InlinePathTarget | null {
const rawValue = value ?? "";
const trimmed = rawValue.trim();
if (!trimmed) {
return null;
}
const match = trimmed.match(/^(.+?):([0-9]+)(?:-([0-9]+))?$/);
if (!match) {
return null;
}
const basePathRaw = match[1]?.trim();
if (!basePathRaw) {
return null;
}
// Avoid accidentally treating URLs as file paths.
if (basePathRaw.includes("://")) {
return null;
}
const normalizedPath = normalizePathToken(basePathRaw);
if (!normalizedPath) {
return null;
}
const lineStart = parseInt(match[2], 10);
if (!Number.isFinite(lineStart) || lineStart <= 0) {
return null;
}
const lineEnd = match[3] ? parseInt(match[3], 10) : undefined;
if (lineEnd !== undefined) {
if (!Number.isFinite(lineEnd) || lineEnd <= 0) {
return null;
}
if (lineEnd < lineStart) {
return null;
}
}
return {
raw: rawValue,
path: normalizedPath,
lineStart,
lineEnd,
};
}