Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# AGENTS.md

This repo is not expected, for now, to be imported as a dependency; treat exported internals as pi-vim-local unless documented otherwise.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Clipboard write mirroring is controlled by `piVim.clipboardMirror`:

The setting controls which local register writes cross the OS clipboard boundary. `p` / `P` keep non-mirrored writes local.

Mode colors: `piVim.modeColors` accepts pi theme tokens (`insert`: `borderMuted`, `normal`: `borderAccent`, `ex`: `warning`); missing keys and unknown tokens use defaults. `piVim.syncBorderColorWithMode` defaults `false`; `true` syncs border to mode, overriding Pi's normal thinking-level border signal.

## wrapping pi-vim

Supported: `pi-vim` first, `@jordyvd/pi-image-attachments` second. pi-vim does not wrap previous editors; wrappers decorate in place or forward the CustomEditor surface: lifecycle (`handleInput`, `render`, `invalidate`), text (`getText`, `setText`, `insertTextAtCursor`, `getExpandedText`), callbacks, `actionHandlers`, flags, reads (`getLines`, `getCursor`, `getMode()`). Inverse order, insert delegates, and generic composition are unsupported.
Expand Down Expand Up @@ -72,7 +74,7 @@ u # undo
2} # jump two paragraphs forward
```

Mode indicator (`INSERT` / `NORMAL` / `EX`) appears bottom-right, theme-colored.
Mode indicator (`INSERT` / `NORMAL` / `EX`) appears bottom-right, theme-colored and configurable.

Requires `@mariozechner/pi-tui >= 0.47.0`. With `pi-tui >= 0.49.3` and DECSCUSR support, cursor shape follows mode; otherwise software cursor remains.

Expand Down
9 changes: 1 addition & 8 deletions biome.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.8/schema.json",
"files": {
"includes": [
"**",
"!node_modules",
"!.pi",
"!.tree",
"!.tmp",
"!doc"
],
"includes": ["**", "!node_modules", "!.pi", "!.tree", "!.tmp", "!doc"],
"ignoreUnknown": true
},
"formatter": {
Expand Down
64 changes: 7 additions & 57 deletions clipboard-policy.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,23 @@
import { SettingsManager } from "@mariozechner/pi-coding-agent";

export type ClipboardMirrorPolicy = "all" | "yank" | "never";
export type RegisterWriteSource = "mutation" | "yank";

export const DEFAULT_CLIPBOARD_MIRROR_POLICY: ClipboardMirrorPolicy = "all";

export type PiVimSettings = { clipboardMirror?: unknown };

type UnknownRecord = Record<string, unknown>;

const missing = Symbol();

function formatInvalid(value: unknown) {
const type = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
function fmt(v: unknown) {
const type = v === null ? "null" : Array.isArray(v) ? "array" : typeof v;
try {
return `${JSON.stringify(value) ?? type} (type ${type})`;
return `${JSON.stringify(v) ?? type} (type ${type})`;
} catch {
return `(type ${type})`;
}
}

function readSetting(settings: unknown): unknown {
if (typeof settings !== "object" || settings === null || !Object.hasOwn(settings, "piVim")) return missing;
const piVim = (settings as UnknownRecord).piVim;
if (typeof piVim !== "object" || piVim === null || Array.isArray(piVim)) return piVim;
return Object.hasOwn(piVim, "clipboardMirror") ? (piVim as UnknownRecord).clipboardMirror : missing;
}

export function resolveClipboardMirrorPolicy(value: unknown) {
if (value === undefined) return { policy: DEFAULT_CLIPBOARD_MIRROR_POLICY };

if (typeof value === "string") {
const policy = value.trim().toLowerCase();
if (policy === "all" || policy === "yank" || policy === "never") {
return { policy: policy as ClipboardMirrorPolicy };
}
}

const p = typeof value === "string" ? value.trim().toLowerCase() : "";
if (p === "all" || p === "yank" || p === "never")
return { policy: p as ClipboardMirrorPolicy };
return {
policy: DEFAULT_CLIPBOARD_MIRROR_POLICY,
warning: `Invalid piVim.clipboardMirror ${formatInvalid(value)}; expected all, yank, never. Using all.`,
};
}

export function readPiVimClipboardMirrorSetting(globalSettings: unknown, projectSettings: unknown): unknown | undefined {
const project = readSetting(projectSettings);
if (project !== missing) return project;
const global = readSetting(globalSettings);
return global === missing ? undefined : global;
}

function readPiVimSettingsFromDisk(cwd: string): PiVimSettings {
const settings = SettingsManager.create(cwd);
return {
clipboardMirror: readPiVimClipboardMirrorSetting(settings.getGlobalSettings(), settings.getProjectSettings()),
};
}

let piVimSettingsReader = readPiVimSettingsFromDisk;

export function readPiVimSettings(cwd: string) {
return piVimSettingsReader(cwd);
}

export function setPiVimSettingsReaderForTests(reader: typeof readPiVimSettingsFromDisk) {
const prev = piVimSettingsReader;
piVimSettingsReader = reader;

return () => {
piVimSettingsReader = prev;
warning: `Invalid piVim.clipboardMirror ${fmt(value)}; expected all, yank, never.`,
};
}
Loading
Loading