mirror of
https://github.com/tiennm99/ccstatusline.git
synced 2026-10-14 02:15:32 +00:00
Support emoji input for custom text
This commit is contained in:
1 parent
dade396de1
commit
17b2576cca
1 file changed
+93
-15
+93
-15
@@ -49,43 +49,121 @@ const CustomTextEditor: React.FC<WidgetEditorProps> = ({ widget, onComplete, onC
|
||||
const [text, setText] = useState(widget.customText ?? '');
|
||||
const [cursorPos, setCursorPos] = useState(text.length);
|
||||
|
||||
// Helper to get grapheme segments if Intl.Segmenter is available
|
||||
const getGraphemes = (str: string): string[] => {
|
||||
if ('Segmenter' in Intl) {
|
||||
const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
||||
return Array.from(segmenter.segment(str), seg => seg.segment);
|
||||
}
|
||||
// Fallback to simple character array (won't handle complex emojis perfectly)
|
||||
return Array.from(str);
|
||||
};
|
||||
|
||||
// Convert between grapheme index and string index
|
||||
const graphemeToStringIndex = (str: string, graphemeIndex: number): number => {
|
||||
const graphemes = getGraphemes(str);
|
||||
let stringIndex = 0;
|
||||
for (let i = 0; i < Math.min(graphemeIndex, graphemes.length); i++) {
|
||||
const grapheme = graphemes[i];
|
||||
if (grapheme) {
|
||||
stringIndex += grapheme.length;
|
||||
}
|
||||
}
|
||||
return stringIndex;
|
||||
};
|
||||
|
||||
const stringToGraphemeIndex = (str: string, stringIndex: number): number => {
|
||||
const graphemes = getGraphemes(str);
|
||||
let currentStringIndex = 0;
|
||||
for (let i = 0; i < graphemes.length; i++) {
|
||||
if (currentStringIndex >= stringIndex)
|
||||
return i;
|
||||
const grapheme = graphemes[i];
|
||||
if (grapheme) {
|
||||
currentStringIndex += grapheme.length;
|
||||
}
|
||||
}
|
||||
return graphemes.length;
|
||||
};
|
||||
|
||||
useInput((input, key) => {
|
||||
if (key.return) {
|
||||
onComplete({ ...widget, customText: text });
|
||||
} else if (key.escape) {
|
||||
onCancel();
|
||||
} else if (key.leftArrow) {
|
||||
setCursorPos(Math.max(0, cursorPos - 1));
|
||||
const currentGraphemeIndex = stringToGraphemeIndex(text, cursorPos);
|
||||
if (currentGraphemeIndex > 0) {
|
||||
const newStringIndex = graphemeToStringIndex(text, currentGraphemeIndex - 1);
|
||||
setCursorPos(newStringIndex);
|
||||
}
|
||||
} else if (key.rightArrow) {
|
||||
setCursorPos(Math.min(text.length, cursorPos + 1));
|
||||
const currentGraphemeIndex = stringToGraphemeIndex(text, cursorPos);
|
||||
const graphemeCount = getGraphemes(text).length;
|
||||
if (currentGraphemeIndex < graphemeCount) {
|
||||
const newStringIndex = graphemeToStringIndex(text, currentGraphemeIndex + 1);
|
||||
setCursorPos(newStringIndex);
|
||||
}
|
||||
} else if (key.ctrl && input === 'ArrowLeft') {
|
||||
setCursorPos(0);
|
||||
} else if (key.ctrl && input === 'ArrowRight') {
|
||||
setCursorPos(text.length);
|
||||
} else if (key.backspace) {
|
||||
if (cursorPos > 0) {
|
||||
setText(text.slice(0, cursorPos - 1) + text.slice(cursorPos));
|
||||
setCursorPos(cursorPos - 1);
|
||||
const currentGraphemeIndex = stringToGraphemeIndex(text, cursorPos);
|
||||
if (currentGraphemeIndex > 0) {
|
||||
const deleteFromIndex = graphemeToStringIndex(text, currentGraphemeIndex - 1);
|
||||
const deleteToIndex = graphemeToStringIndex(text, currentGraphemeIndex);
|
||||
setText(text.slice(0, deleteFromIndex) + text.slice(deleteToIndex));
|
||||
setCursorPos(deleteFromIndex);
|
||||
}
|
||||
}
|
||||
} else if (key.delete) {
|
||||
if (cursorPos < text.length) {
|
||||
setText(text.slice(0, cursorPos) + text.slice(cursorPos + 1));
|
||||
const currentGraphemeIndex = stringToGraphemeIndex(text, cursorPos);
|
||||
const graphemeCount = getGraphemes(text).length;
|
||||
if (currentGraphemeIndex < graphemeCount) {
|
||||
const deleteFromIndex = graphemeToStringIndex(text, currentGraphemeIndex);
|
||||
const deleteToIndex = graphemeToStringIndex(text, currentGraphemeIndex + 1);
|
||||
setText(text.slice(0, deleteFromIndex) + text.slice(deleteToIndex));
|
||||
}
|
||||
}
|
||||
} else if (input && input.length === 1) {
|
||||
setText(text.slice(0, cursorPos) + input + text.slice(cursorPos));
|
||||
setCursorPos(cursorPos + 1);
|
||||
} else if (input && !key.ctrl && !key.meta) {
|
||||
// Insert the input at cursor position
|
||||
const newText = text.slice(0, cursorPos) + input + text.slice(cursorPos);
|
||||
setText(newText);
|
||||
|
||||
// Move cursor by the actual string length of the input
|
||||
// This handles multi-byte characters including emojis with modifiers
|
||||
setCursorPos(cursorPos + input.length);
|
||||
}
|
||||
});
|
||||
|
||||
// Get the grapheme at cursor position for display
|
||||
const graphemes = getGraphemes(text);
|
||||
const currentGraphemeIndex = stringToGraphemeIndex(text, cursorPos);
|
||||
|
||||
// Build display with ANSI codes for cursor highlighting
|
||||
let display = 'Enter custom text: ';
|
||||
for (let i = 0; i < graphemes.length; i++) {
|
||||
const grapheme = graphemes[i];
|
||||
if (grapheme) {
|
||||
if (i === currentGraphemeIndex) {
|
||||
// Use inverse video for cursor position
|
||||
display += `\x1b[7m${grapheme}\x1b[0m`;
|
||||
} else {
|
||||
display += grapheme;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentGraphemeIndex >= graphemes.length) {
|
||||
// Cursor at end
|
||||
display += '\x1b[7m \x1b[0m';
|
||||
}
|
||||
|
||||
return (
|
||||
<Box flexDirection='column'>
|
||||
<Text>
|
||||
Enter custom text:
|
||||
{' '}
|
||||
{text.slice(0, cursorPos)}
|
||||
<Text backgroundColor='gray' color='black'>{text[cursorPos] ?? ' '}</Text>
|
||||
{text.slice(cursorPos + 1)}
|
||||
</Text>
|
||||
<Text>{display}</Text>
|
||||
<Text dimColor>←→ move cursor, Ctrl+←→ jump to start/end, Enter save, ESC cancel</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user