@nanocollective/nanocoder
Version:
A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter
180 lines • 6.56 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import chalk from 'chalk';
import { Text, useInput } from 'ink';
import { useEffect, useState } from 'react';
import { wrapWithTrimmedContinuations } from '../utils/text-wrapping.js';
function TextInput({ value: originalValue, placeholder = '', focus = true, mask, highlightPastedText = false, showCursor = true, onChange, onSubmit, wrapWidth, }) {
const [state, setState] = useState({
cursorOffset: (originalValue || '').length,
cursorWidth: 0,
});
const { cursorOffset, cursorWidth } = state;
useEffect(() => {
setState(previousState => {
if (!focus || !showCursor) {
return previousState;
}
const newValue = originalValue || '';
if (previousState.cursorOffset > newValue.length - 1) {
return {
cursorOffset: newValue.length,
cursorWidth: 0,
};
}
return previousState;
});
}, [originalValue, focus, showCursor]);
const cursorActualWidth = highlightPastedText ? cursorWidth : 0;
const value = mask ? mask.repeat(originalValue.length) : originalValue;
let renderedValue = value;
let renderedPlaceholder = placeholder ? chalk.grey(placeholder) : undefined;
if (showCursor && focus) {
renderedPlaceholder =
placeholder.length > 0
? chalk.inverse(placeholder[0]) + chalk.grey(placeholder.slice(1))
: chalk.inverse(' ');
renderedValue = value.length > 0 ? '' : chalk.inverse(' ');
let i = 0;
for (const char of value) {
renderedValue +=
i >= cursorOffset - cursorActualWidth && i <= cursorOffset
? chalk.inverse(char)
: char;
i++;
}
if (value.length > 0 && cursorOffset === value.length) {
renderedValue += chalk.inverse(' ');
}
}
useInput((input, key) => {
if (key.upArrow ||
key.downArrow ||
(key.ctrl && input === 'c') ||
key.tab ||
(key.shift && key.tab)) {
return;
}
if (key.return) {
if (onSubmit) {
onSubmit(originalValue);
}
return;
}
let nextCursorOffset = cursorOffset;
let nextValue = originalValue;
let nextCursorWidth = 0;
if (key.ctrl) {
// Readline keybinds
switch (input) {
case 'a': {
// Move cursor to start of line
nextCursorOffset = 0;
break;
}
case 'e': {
// Move cursor to end of line
nextCursorOffset = originalValue.length;
break;
}
case 'b': {
// Move cursor back one character
if (showCursor) {
nextCursorOffset--;
}
break;
}
case 'f': {
// Move cursor forward one character
if (showCursor) {
nextCursorOffset++;
}
break;
}
case 'w': {
// Delete previous word (backward-kill-word)
if (cursorOffset > 0) {
let i = cursorOffset;
// Skip whitespace immediately before cursor
while (i > 0 && originalValue[i - 1] === ' ') {
i--;
}
// Delete back to next whitespace or start
while (i > 0 && originalValue[i - 1] !== ' ') {
i--;
}
nextValue =
originalValue.slice(0, i) + originalValue.slice(cursorOffset);
nextCursorOffset = i;
}
break;
}
case 'u': {
// Delete from cursor to start of line
nextValue = originalValue.slice(cursorOffset);
nextCursorOffset = 0;
break;
}
case 'k': {
// Delete from cursor to end of line
nextValue = originalValue.slice(0, cursorOffset);
break;
}
default:
// Ignore all other ctrl combinations (don't insert characters)
break;
}
}
else if (key.leftArrow) {
if (showCursor) {
nextCursorOffset--;
}
}
else if (key.rightArrow) {
if (showCursor) {
nextCursorOffset++;
}
}
else if (key.backspace || key.delete) {
if (cursorOffset > 0) {
nextValue =
originalValue.slice(0, cursorOffset - 1) +
originalValue.slice(cursorOffset, originalValue.length);
nextCursorOffset--;
}
}
else {
nextValue =
originalValue.slice(0, cursorOffset) +
input +
originalValue.slice(cursorOffset, originalValue.length);
nextCursorOffset += input.length;
if (input.length > 1) {
nextCursorWidth = input.length;
}
}
if (nextCursorOffset < 0) {
nextCursorOffset = 0;
}
if (nextCursorOffset > nextValue.length) {
nextCursorOffset = nextValue.length;
}
setState({
cursorOffset: nextCursorOffset,
cursorWidth: nextCursorWidth,
});
if (nextValue !== originalValue) {
onChange(nextValue);
}
}, { isActive: focus });
const finalValue = placeholder
? value.length > 0
? renderedValue
: renderedPlaceholder
: renderedValue;
const displayValue = wrapWidth && wrapWidth > 0 && finalValue
? wrapWithTrimmedContinuations(finalValue, wrapWidth)
: finalValue;
return _jsx(Text, { children: displayValue });
}
export default TextInput;
//# sourceMappingURL=text-input.js.map