UNPKG

dmux

Version:

Tmux pane manager with AI agent integration for parallel development workflows

223 lines 9.09 kB
import React, { useState, useEffect } from 'react'; import { Box, Text, useInput, useFocus } from 'ink'; const SimpleGeminiInput = ({ value, onChange, onSubmit, onCancel, multiline = false, placeholder = '' }) => { const { isFocused } = useFocus({ autoFocus: true }); const [cursorOffset, setCursorOffset] = useState(0); // Keep cursor in bounds when value changes useEffect(() => { if (cursorOffset > value.length) { setCursorOffset(value.length); } }, [value, cursorOffset]); useInput((input, key) => { if (!isFocused) return; // Handle special keys if (key.escape) { onCancel?.(); return; } if (key.return && !multiline) { onSubmit?.(); return; } if (key.return && multiline && !key.shift) { // Add newline in multiline mode const before = value.slice(0, cursorOffset); const after = value.slice(cursorOffset); onChange(before + '\n' + after); setCursorOffset(cursorOffset + 1); return; } if (key.return && multiline && key.shift) { // Shift+Enter submits in multiline mode onSubmit?.(); return; } // Handle backspace - delete character BEFORE cursor if (key.backspace) { if (cursorOffset > 0) { const before = value.slice(0, cursorOffset - 1); const after = value.slice(cursorOffset); onChange(before + after); setCursorOffset(cursorOffset - 1); } return; } // Handle delete - delete character AT cursor if (key.delete) { if (cursorOffset < value.length) { const before = value.slice(0, cursorOffset); const after = value.slice(cursorOffset + 1); onChange(before + after); // Cursor stays in same position } return; } // Handle arrow keys if (key.leftArrow) { if (cursorOffset > 0) { setCursorOffset(cursorOffset - 1); } return; } if (key.rightArrow) { if (cursorOffset < value.length) { setCursorOffset(cursorOffset + 1); } return; } // Handle up/down for multiline if (multiline) { const lines = value.split('\n'); let currentPos = 0; let currentLine = 0; let colInLine = 0; // Find current line and column for (let i = 0; i < lines.length; i++) { const lineLength = lines[i].length; if (currentPos + lineLength >= cursorOffset) { currentLine = i; colInLine = cursorOffset - currentPos; break; } currentPos += lineLength + 1; // +1 for newline } if (key.upArrow) { if (currentLine > 0) { // Move to previous line, same column or end of line const prevLine = lines[currentLine - 1]; const newCol = Math.min(colInLine, prevLine.length); let newOffset = 0; for (let i = 0; i < currentLine - 1; i++) { newOffset += lines[i].length + 1; } newOffset += newCol; setCursorOffset(newOffset); } else { // Already at first line, move to start setCursorOffset(0); } return; } if (key.downArrow) { if (currentLine < lines.length - 1) { // Move to next line, same column or end of line const nextLine = lines[currentLine + 1]; const newCol = Math.min(colInLine, nextLine.length); let newOffset = 0; for (let i = 0; i <= currentLine; i++) { newOffset += lines[i].length + 1; } newOffset += newCol; setCursorOffset(newOffset); } else { // Already at last line, move to end setCursorOffset(value.length); } return; } } // Handle Home/End if (key.ctrl && input === 'a') { if (multiline) { // Move to start of current line const beforeCursor = value.slice(0, cursorOffset); const lastNewline = beforeCursor.lastIndexOf('\n'); setCursorOffset(lastNewline === -1 ? 0 : lastNewline + 1); } else { setCursorOffset(0); } return; } if (key.ctrl && input === 'e') { if (multiline) { // Move to end of current line const afterCursor = value.slice(cursorOffset); const nextNewline = afterCursor.indexOf('\n'); if (nextNewline === -1) { setCursorOffset(value.length); } else { setCursorOffset(cursorOffset + nextNewline); } } else { setCursorOffset(value.length); } return; } // Handle regular text input if (input && !key.ctrl && !key.meta) { const before = value.slice(0, cursorOffset); const after = value.slice(cursorOffset); onChange(before + input + after); setCursorOffset(cursorOffset + input.length); } }); // Render the display with cursor const renderDisplay = () => { // Show placeholder when empty if (value === '' && placeholder) { return (React.createElement(React.Fragment, null, React.createElement(Text, { dimColor: true }, placeholder), React.createElement(Text, { inverse: true }, ' '))); } // For multiline, split and render each line if (multiline) { const lines = value.split('\n'); let currentPos = 0; const elements = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const lineStart = currentPos; const lineEnd = currentPos + line.length; if (i > 0) { elements.push(React.createElement(Text, { key: `indent-${i}` }, ' ')); } if (cursorOffset >= lineStart && cursorOffset <= lineEnd) { // This line contains the cursor const posInLine = cursorOffset - lineStart; const before = line.slice(0, posInLine); const cursorChar = line[posInLine] || ' '; const after = line.slice(posInLine + 1); elements.push(React.createElement(React.Fragment, { key: `line-${i}` }, React.createElement(Text, null, before), React.createElement(Text, { inverse: true }, cursorChar), React.createElement(Text, null, after))); } else { // Normal line without cursor elements.push(React.createElement(Text, { key: `line-${i}` }, line || ' ')); } // Add newline representation except for last line if (i < lines.length - 1) { elements.push(React.createElement(Text, { key: `newline-${i}` }, '\n')); } currentPos = lineEnd + 1; // +1 for newline } // Handle cursor at very end after a newline if (cursorOffset === value.length && value.endsWith('\n')) { elements.push(React.createElement(Text, { key: "indent-end" }, ' ')); elements.push(React.createElement(Text, { key: "cursor-end", inverse: true }, ' ')); } return React.createElement(React.Fragment, null, elements); } // Single line rendering const before = value.slice(0, cursorOffset); const cursorChar = value[cursorOffset] || ' '; const after = value.slice(cursorOffset + 1); return (React.createElement(React.Fragment, null, React.createElement(Text, null, before), React.createElement(Text, { inverse: true }, cursorChar), React.createElement(Text, null, after))); }; return (React.createElement(Box, null, React.createElement(Text, null, '> '), renderDisplay())); }; export default SimpleGeminiInput; //# sourceMappingURL=SimpleGeminiInput.js.map