feat: add text clipboard copy/read with mouse selection support
Add cross-platform text clipboard operations (macOS, Linux, Windows) with OSC 52 support for SSH/tmux environments. Wire up onMouseUp and Ctrl+Y in the TUI to copy selected text to the system clipboard via OpenTUI's renderer selection API.
This commit is contained in:
23
src/constants/clipboard.ts
Normal file
23
src/constants/clipboard.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Clipboard constants for text copy/read operations
|
||||
*/
|
||||
|
||||
/** OSC 52 escape sequence for clipboard write (insert base64 payload) */
|
||||
export const OSC52_SEQUENCE_PREFIX = "\x1b]52;c;";
|
||||
export const OSC52_SEQUENCE_SUFFIX = "\x07";
|
||||
|
||||
/** DCS tmux passthrough wrapping */
|
||||
export const TMUX_DCS_PREFIX = "\x1bPtmux;\x1b";
|
||||
export const TMUX_DCS_SUFFIX = "\x1b\\";
|
||||
|
||||
/** Environment variables indicating tmux/screen session */
|
||||
export const TMUX_ENV_VAR = "TMUX";
|
||||
export const SCREEN_ENV_VAR = "STY";
|
||||
export const WAYLAND_DISPLAY_ENV_VAR = "WAYLAND_DISPLAY";
|
||||
|
||||
/** Platform command timeout in milliseconds */
|
||||
export const CLIPBOARD_COMMAND_TIMEOUT_MS = 5000;
|
||||
|
||||
/** Supported MIME types for clipboard content */
|
||||
export const CLIPBOARD_MIME_TEXT = "text/plain" as const;
|
||||
export const CLIPBOARD_MIME_IMAGE_PNG = "image/png" as const;
|
||||
@@ -26,11 +26,38 @@ export const PARTIAL_X10_REGEX = /\x1b\[M.{0,2}$/;
|
||||
// Mouse tracking escape sequences
|
||||
export const MOUSE_TRACKING_SEQUENCES = {
|
||||
ENABLE_BUTTON: "\x1b[?1000h",
|
||||
ENABLE_BUTTON_EVENT: "\x1b[?1002h",
|
||||
ENABLE_SGR: "\x1b[?1006h",
|
||||
DISABLE_SGR: "\x1b[?1006l",
|
||||
DISABLE_BUTTON_EVENT: "\x1b[?1002l",
|
||||
DISABLE_BUTTON: "\x1b[?1000l",
|
||||
} as const;
|
||||
|
||||
// Enable button-event tracking + SGR encoding (for scroll + drag selection)
|
||||
export const MOUSE_TRACKING_ENABLE =
|
||||
MOUSE_TRACKING_SEQUENCES.ENABLE_BUTTON_EVENT +
|
||||
MOUSE_TRACKING_SEQUENCES.ENABLE_SGR;
|
||||
|
||||
// Mouse button action codes (SGR encoding)
|
||||
export const MOUSE_BUTTON = {
|
||||
LEFT_PRESS: 0,
|
||||
MIDDLE_PRESS: 1,
|
||||
RIGHT_PRESS: 2,
|
||||
LEFT_DRAG: 32,
|
||||
MIDDLE_DRAG: 33,
|
||||
RIGHT_DRAG: 34,
|
||||
SCROLL_UP: 64,
|
||||
SCROLL_DOWN: 65,
|
||||
} as const;
|
||||
|
||||
// Disable button-event tracking + SGR encoding
|
||||
export const MOUSE_TRACKING_DISABLE =
|
||||
MOUSE_TRACKING_SEQUENCES.DISABLE_BUTTON_EVENT +
|
||||
MOUSE_TRACKING_SEQUENCES.DISABLE_SGR;
|
||||
|
||||
// Time in ms to re-enable mouse tracking after selection ends
|
||||
export const MOUSE_SELECTION_REENABLE_MS = 2000;
|
||||
|
||||
// Scroll direction type
|
||||
export type MouseScrollDirection = "up" | "down";
|
||||
|
||||
|
||||
58
src/constants/plan-approval.ts
Normal file
58
src/constants/plan-approval.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Plan Approval Constants
|
||||
*
|
||||
* Options and configuration for the plan approval prompt,
|
||||
* modeled after Claude Code's plan approval flow.
|
||||
*/
|
||||
|
||||
/** How edits should be handled after plan approval */
|
||||
export type PlanEditMode =
|
||||
| "auto_accept_clear" // Clear context and auto-accept edits
|
||||
| "auto_accept" // Auto-accept edits
|
||||
| "manual_approve" // Manually approve each edit
|
||||
| "feedback"; // User provides feedback/changes
|
||||
|
||||
/** A selectable option in the plan approval modal */
|
||||
export interface PlanApprovalOption {
|
||||
key: string;
|
||||
label: string;
|
||||
description: string;
|
||||
editMode: PlanEditMode;
|
||||
shortcut?: string;
|
||||
}
|
||||
|
||||
/** Available plan approval options, matching Claude Code's pattern */
|
||||
export const PLAN_APPROVAL_OPTIONS: PlanApprovalOption[] = [
|
||||
{
|
||||
key: "1",
|
||||
label: "Yes, clear context and auto-accept edits",
|
||||
description: "Approve plan, clear conversation context, and auto-accept all file edits",
|
||||
editMode: "auto_accept_clear",
|
||||
shortcut: "shift+tab",
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
label: "Yes, auto-accept edits",
|
||||
description: "Approve plan and auto-accept all file edits without prompting",
|
||||
editMode: "auto_accept",
|
||||
},
|
||||
{
|
||||
key: "3",
|
||||
label: "Yes, manually approve edits",
|
||||
description: "Approve plan but require manual approval for each file edit",
|
||||
editMode: "manual_approve",
|
||||
},
|
||||
{
|
||||
key: "4",
|
||||
label: "Type here to tell CodeTyper what to change",
|
||||
description: "Provide feedback or modifications to the plan",
|
||||
editMode: "feedback",
|
||||
},
|
||||
];
|
||||
|
||||
/** Maximum visible options before scrolling */
|
||||
export const PLAN_APPROVAL_MAX_VISIBLE_OPTIONS = 4;
|
||||
|
||||
/** Footer help text for plan approval modal */
|
||||
export const PLAN_APPROVAL_FOOTER_TEXT =
|
||||
"ctrl-g to edit in editor";
|
||||
@@ -7,3 +7,14 @@ export const DISABLE_MOUSE_TRACKING =
|
||||
"\x1b[?1003l" + // All mouse events (motion)
|
||||
"\x1b[?1002l" + // Button event mouse tracking
|
||||
"\x1b[?1000l"; // Normal tracking mode
|
||||
|
||||
/**
|
||||
* Full terminal reset sequence for cleanup on exit
|
||||
* Disables all tracking modes, restores cursor, exits alternate screen
|
||||
*/
|
||||
export const TERMINAL_RESET =
|
||||
DISABLE_MOUSE_TRACKING +
|
||||
"\x1b[?25h" + // Show cursor
|
||||
"\x1b[?1049l" + // Leave alternate screen
|
||||
"\x1b[>4;0m" + // Disable Kitty keyboard protocol (pop)
|
||||
"\x1b[?2004l"; // Disable bracketed paste mode
|
||||
|
||||
30
src/constants/thinking-tags.ts
Normal file
30
src/constants/thinking-tags.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Thinking Tags Constants
|
||||
*
|
||||
* XML tag names that LLM providers use for reasoning/thinking blocks.
|
||||
* These should be stripped from visible streaming output and displayed
|
||||
* as dimmed thinking indicators instead.
|
||||
*/
|
||||
|
||||
/** Tag names to strip from streaming content */
|
||||
export const THINKING_TAGS = [
|
||||
"thinking",
|
||||
"search",
|
||||
"plan",
|
||||
"execute",
|
||||
] as const;
|
||||
|
||||
export type ThinkingTagName = (typeof THINKING_TAGS)[number];
|
||||
|
||||
/** Regex pattern to match a complete opening tag: <thinking>, <search>, etc. */
|
||||
export const THINKING_OPEN_TAG_REGEX = new RegExp(
|
||||
`<(${THINKING_TAGS.join("|")})>`,
|
||||
);
|
||||
|
||||
/** Regex pattern to match a complete closing tag: </thinking>, </search>, etc. */
|
||||
export const THINKING_CLOSE_TAG_REGEX = new RegExp(
|
||||
`</(${THINKING_TAGS.join("|")})>`,
|
||||
);
|
||||
|
||||
/** Maximum buffer size before flushing as visible text (safety valve) */
|
||||
export const THINKING_BUFFER_MAX_SIZE = 256;
|
||||
Reference in New Issue
Block a user