Refactor TextBuffer to be a React hook (#340)
This commit is contained in:
parent
7116ab9c29
commit
bfda4295c9
|
@ -4,10 +4,10 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { TextBuffer } from './text-buffer.js';
|
import { useTextBuffer } from './text-buffer.js';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { Box, Text, useInput, useStdin, Key } from 'ink';
|
import { Box, Text, useInput, useStdin, Key } from 'ink';
|
||||||
import React, { useState, useCallback } from 'react';
|
import React from 'react';
|
||||||
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
|
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
|
||||||
import { Colors } from '../../colors.js';
|
import { Colors } from '../../colors.js';
|
||||||
|
|
||||||
|
@ -68,10 +68,6 @@ export const MultilineTextEditor = ({
|
||||||
navigateDown,
|
navigateDown,
|
||||||
inputPreprocessor,
|
inputPreprocessor,
|
||||||
}: MultilineTextEditorProps): React.ReactElement => {
|
}: MultilineTextEditorProps): React.ReactElement => {
|
||||||
const [buffer, setBuffer] = useState(
|
|
||||||
() => new TextBuffer(initialText, initialCursorOffset),
|
|
||||||
);
|
|
||||||
|
|
||||||
const terminalSize = useTerminalSize();
|
const terminalSize = useTerminalSize();
|
||||||
const effectiveWidth = Math.max(
|
const effectiveWidth = Math.max(
|
||||||
20,
|
20,
|
||||||
|
@ -81,35 +77,14 @@ export const MultilineTextEditor = ({
|
||||||
|
|
||||||
const { stdin, setRawMode } = useStdin();
|
const { stdin, setRawMode } = useStdin();
|
||||||
|
|
||||||
// TODO(jacobr): make TextBuffer immutable rather than this hack to act
|
const buffer = useTextBuffer({
|
||||||
// like it is immutable.
|
initialText,
|
||||||
const updateBufferState = useCallback(
|
initialCursorOffset,
|
||||||
(mutator: (currentBuffer: TextBuffer) => void) => {
|
viewport: { height, width: effectiveWidth },
|
||||||
setBuffer((currentBuffer) => {
|
stdin,
|
||||||
mutator(currentBuffer);
|
setRawMode,
|
||||||
// Create a new instance from the mutated buffer to trigger re-render
|
onChange, // Pass onChange to the hook
|
||||||
return TextBuffer.fromBuffer(currentBuffer);
|
|
||||||
});
|
});
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const openExternalEditor = useCallback(async () => {
|
|
||||||
const wasRaw = stdin?.isRaw ?? false;
|
|
||||||
try {
|
|
||||||
setRawMode?.(false);
|
|
||||||
// openInExternalEditor mutates the buffer instance
|
|
||||||
await buffer.openInExternalEditor();
|
|
||||||
} catch (err) {
|
|
||||||
console.error('[MultilineTextEditor] external editor error', err);
|
|
||||||
} finally {
|
|
||||||
if (wasRaw) {
|
|
||||||
setRawMode?.(true);
|
|
||||||
}
|
|
||||||
// Update state with the mutated buffer to trigger re-render
|
|
||||||
setBuffer(TextBuffer.fromBuffer(buffer));
|
|
||||||
}
|
|
||||||
}, [buffer, stdin, setRawMode, setBuffer]);
|
|
||||||
|
|
||||||
useInput(
|
useInput(
|
||||||
(input, key) => {
|
(input, key) => {
|
||||||
|
@ -131,7 +106,7 @@ export const MultilineTextEditor = ({
|
||||||
input.length === 1 &&
|
input.length === 1 &&
|
||||||
input.charCodeAt(0) === 5);
|
input.charCodeAt(0) === 5);
|
||||||
if (isCtrlX || isCtrlE) {
|
if (isCtrlX || isCtrlE) {
|
||||||
openExternalEditor();
|
buffer.openInExternalEditor();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,8 +117,6 @@ export const MultilineTextEditor = ({
|
||||||
console.log('[MultilineTextEditor] event', { input, key });
|
console.log('[MultilineTextEditor] event', { input, key });
|
||||||
}
|
}
|
||||||
|
|
||||||
let bufferMutated = false;
|
|
||||||
|
|
||||||
if (input.startsWith('[') && input.endsWith('u')) {
|
if (input.startsWith('[') && input.endsWith('u')) {
|
||||||
const m = input.match(/^\[([0-9]+);([0-9]+)u$/);
|
const m = input.match(/^\[([0-9]+);([0-9]+)u$/);
|
||||||
if (m && m[1] === '13') {
|
if (m && m[1] === '13') {
|
||||||
|
@ -151,14 +124,10 @@ export const MultilineTextEditor = ({
|
||||||
const hasCtrl = Math.floor(mod / 4) % 2 === 1;
|
const hasCtrl = Math.floor(mod / 4) % 2 === 1;
|
||||||
if (hasCtrl) {
|
if (hasCtrl) {
|
||||||
if (onSubmit) {
|
if (onSubmit) {
|
||||||
onSubmit(buffer.getText());
|
onSubmit(buffer.text);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
buffer.newline();
|
buffer.newline();
|
||||||
bufferMutated = true;
|
|
||||||
}
|
|
||||||
if (bufferMutated) {
|
|
||||||
updateBufferState((_) => {}); // Trigger re-render if mutated
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -171,14 +140,10 @@ export const MultilineTextEditor = ({
|
||||||
const hasCtrl = Math.floor(mod / 4) % 2 === 1;
|
const hasCtrl = Math.floor(mod / 4) % 2 === 1;
|
||||||
if (hasCtrl) {
|
if (hasCtrl) {
|
||||||
if (onSubmit) {
|
if (onSubmit) {
|
||||||
onSubmit(buffer.getText());
|
onSubmit(buffer.text);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
buffer.newline();
|
buffer.newline();
|
||||||
bufferMutated = true;
|
|
||||||
}
|
|
||||||
if (bufferMutated) {
|
|
||||||
updateBufferState((_) => {}); // Trigger re-render if mutated
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -186,63 +151,42 @@ export const MultilineTextEditor = ({
|
||||||
|
|
||||||
if (input === '\n') {
|
if (input === '\n') {
|
||||||
buffer.newline();
|
buffer.newline();
|
||||||
updateBufferState((_) => {});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input === '\r') {
|
if (input === '\r') {
|
||||||
if (onSubmit) {
|
if (onSubmit) {
|
||||||
onSubmit(buffer.getText());
|
onSubmit(buffer.text);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.upArrow) {
|
if (key.upArrow) {
|
||||||
if (buffer.getCursor()[0] === 0 && navigateUp) {
|
if (buffer.cursor[0] === 0 && navigateUp) {
|
||||||
navigateUp();
|
navigateUp();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key.downArrow) {
|
if (key.downArrow) {
|
||||||
if (
|
if (buffer.cursor[0] === buffer.lines.length - 1 && navigateDown) {
|
||||||
buffer.getCursor()[0] === buffer.getText().split('\n').length - 1 &&
|
|
||||||
navigateDown
|
|
||||||
) {
|
|
||||||
navigateDown();
|
navigateDown();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const modifiedByHandleInput = buffer.handleInput(
|
buffer.handleInput(input, key as Record<string, boolean>);
|
||||||
input,
|
|
||||||
key as Record<string, boolean>,
|
|
||||||
{ height, width: effectiveWidth },
|
|
||||||
);
|
|
||||||
|
|
||||||
if (modifiedByHandleInput) {
|
|
||||||
updateBufferState((_) => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newText = buffer.getText();
|
|
||||||
if (onChange) {
|
|
||||||
onChange(newText);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{ isActive: focus },
|
{ isActive: focus },
|
||||||
);
|
);
|
||||||
|
|
||||||
const visibleLines = buffer.getVisibleLines({
|
const visibleLines = buffer.visibleLines;
|
||||||
height,
|
const [cursorRow, cursorCol] = buffer.cursor;
|
||||||
width: effectiveWidth,
|
const [scrollRow, scrollCol] = buffer.scroll;
|
||||||
});
|
|
||||||
const [cursorRow, cursorCol] = buffer.getCursor();
|
|
||||||
const scrollRow = buffer.getScrollRow();
|
|
||||||
const scrollCol = buffer.getScrollCol();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column">
|
<Box flexDirection="column">
|
||||||
{buffer.getText().length === 0 && placeholder ? (
|
{buffer.text.length === 0 && placeholder ? (
|
||||||
<Text color={Colors.SubtleComment}>{placeholder}</Text>
|
<Text color={Colors.SubtleComment}>{placeholder}</Text>
|
||||||
) : (
|
) : (
|
||||||
visibleLines.map((lineText, idx) => {
|
visibleLines.map((lineText, idx) => {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue