fix(edit-selection): accurate per-condition warnings + path-boundary workspace check
cowriting.editSelection collapsed four distinct failures (no editor / no selection / unsaved / outside the workspace folder) into one misleading "select some text in a workspace document first" warning — so editing a selection in a file OUTSIDE the EDH workspace root (e.g. a content-repo file while the EDH opens sandbox/) was rejected as if no text were selected. - New pure, vscode-free src/workspacePath.ts: isUnderRoot() (separator-bounded membership, fixing a latent startsWith() prefix collision where a sibling whose name prefixes the root — e.g. vscode-cowriting-plugin-content vs vscode-cowriting-plugin — falsely matched) + selectionRejection() (one message per condition). Unit-tested (8 cases), incl. the reported bug. - Wire isUnderRoot into all five membership checks: extension.ts (editSelection guard + renderIfOpen) and the thread/attribution/proposal controllers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* workspacePath — pure, vscode-free helpers for the "is this document one
|
||||
* Cowriting can anchor to?" decision (F2/F3/F4 require a SAVED file under the
|
||||
* workspace folder, because threads/attribution/proposals persist to a
|
||||
* `.threads/` sidecar beside that file).
|
||||
*
|
||||
* Split out so the membership test and the per-condition warning are
|
||||
* deterministic and unit-testable with no editor — see `test/workspacePath.test.ts`.
|
||||
*/
|
||||
import * as path from "node:path";
|
||||
|
||||
/**
|
||||
* Is `fsPath` the workspace root or a path strictly inside it?
|
||||
*
|
||||
* Uses a path-separator boundary, NOT a bare `startsWith(root)` — otherwise a
|
||||
* sibling whose name merely begins with the root's name would falsely match
|
||||
* (e.g. `.../vscode-cowriting-plugin-content/x` vs root `.../vscode-cowriting-plugin`).
|
||||
*/
|
||||
export function isUnderRoot(fsPath: string, root: string): boolean {
|
||||
return fsPath === root || fsPath.startsWith(root + path.sep);
|
||||
}
|
||||
|
||||
export interface SelectionContext {
|
||||
/** Is there an active text editor at all? */
|
||||
hasEditor: boolean;
|
||||
/** Is the active editor's selection empty (no highlight)? */
|
||||
selectionEmpty: boolean;
|
||||
/** The active document's URI scheme (`file`, `untitled`, …). */
|
||||
scheme: string;
|
||||
/** The active document's filesystem path (empty if none). */
|
||||
fsPath: string;
|
||||
/** The workspace folder Cowriting is anchored to. */
|
||||
root: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Why a selection can't be sent to Claude — or `null` if it can. Each condition
|
||||
* gets its OWN message so the user learns the real reason instead of always
|
||||
* being told to "select some text" (the reported bug: an out-of-workspace file
|
||||
* was rejected with the no-selection message even though text was selected).
|
||||
*/
|
||||
export function selectionRejection(ctx: SelectionContext): string | null {
|
||||
if (!ctx.hasEditor) {
|
||||
return "Cowriting: focus a text editor with a selection first.";
|
||||
}
|
||||
if (ctx.selectionEmpty) {
|
||||
return "Cowriting: select some text to send to Claude first.";
|
||||
}
|
||||
if (ctx.scheme !== "file") {
|
||||
return "Cowriting: save this document to a file first — Cowriting anchors edits to a saved workspace file.";
|
||||
}
|
||||
if (!isUnderRoot(ctx.fsPath, ctx.root)) {
|
||||
return "Cowriting: this file is outside your workspace folder — open a file inside it to ask Claude to edit (Cowriting anchors edits and attribution to files in the workspace).";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user