Implement additional readline-like keybindings, including alt-left arrow and alt-right arrow. (#443)
This change adds keybinding support for: - `Ctrl+B`: Moves the cursor backward one character. - `Ctrl+F`: Moves the cursor forward one character. - `Alt+Left Arrow`: Moves the cursor backward one word. - `Alt+Right Arrow`: Moves the cursor forward one word. Closes b/411469305.
This commit is contained in:
parent
6ca446bded
commit
ee702c3139
|
@ -375,7 +375,6 @@ export const App = ({
|
||||||
navigateSuggestionUp={completion.navigateUp}
|
navigateSuggestionUp={completion.navigateUp}
|
||||||
navigateSuggestionDown={completion.navigateDown}
|
navigateSuggestionDown={completion.navigateDown}
|
||||||
resetCompletion={completion.resetCompletionState}
|
resetCompletion={completion.resetCompletionState}
|
||||||
setEditorState={setEditorState}
|
|
||||||
onClearScreen={handleClearScreen} // Added onClearScreen prop
|
onClearScreen={handleClearScreen} // Added onClearScreen prop
|
||||||
shellModeActive={shellModeActive}
|
shellModeActive={shellModeActive}
|
||||||
setShellModeActive={setShellModeActive}
|
setShellModeActive={setShellModeActive}
|
||||||
|
|
|
@ -24,7 +24,6 @@ interface InputPromptProps {
|
||||||
userMessages: readonly string[];
|
userMessages: readonly string[];
|
||||||
navigateSuggestionUp: () => void;
|
navigateSuggestionUp: () => void;
|
||||||
navigateSuggestionDown: () => void;
|
navigateSuggestionDown: () => void;
|
||||||
setEditorState: (updater: (prevState: EditorState) => EditorState) => void;
|
|
||||||
onClearScreen: () => void;
|
onClearScreen: () => void;
|
||||||
shellModeActive: boolean;
|
shellModeActive: boolean;
|
||||||
setShellModeActive: (value: boolean) => void;
|
setShellModeActive: (value: boolean) => void;
|
||||||
|
@ -48,7 +47,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||||
navigateSuggestionUp,
|
navigateSuggestionUp,
|
||||||
navigateSuggestionDown,
|
navigateSuggestionDown,
|
||||||
resetCompletion,
|
resetCompletion,
|
||||||
setEditorState,
|
|
||||||
onClearScreen,
|
onClearScreen,
|
||||||
shellModeActive,
|
shellModeActive,
|
||||||
setShellModeActive,
|
setShellModeActive,
|
||||||
|
@ -114,12 +112,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
const inputPreprocessor = useCallback(
|
const inputPreprocessor = useCallback(
|
||||||
(
|
(input: string, key: Key) => {
|
||||||
input: string,
|
|
||||||
key: Key,
|
|
||||||
_currentText?: string,
|
|
||||||
_cursorOffset?: number,
|
|
||||||
) => {
|
|
||||||
if (input === '!' && query === '' && !showSuggestions) {
|
if (input === '!' && query === '' && !showSuggestions) {
|
||||||
setShellModeActive(!shellModeActive);
|
setShellModeActive(!shellModeActive);
|
||||||
onChangeAndMoveCursor(''); // Clear the '!' from input
|
onChangeAndMoveCursor(''); // Clear the '!' from input
|
||||||
|
@ -156,17 +149,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Keybindings when suggestions are not shown
|
// Keybindings when suggestions are not shown
|
||||||
if (key.ctrl && input === 'a') {
|
|
||||||
setEditorState((s) => ({ key: s.key + 1, initialCursorOffset: 0 }));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (key.ctrl && input === 'e') {
|
|
||||||
setEditorState((s) => ({
|
|
||||||
key: s.key + 1,
|
|
||||||
initialCursorOffset: query.length,
|
|
||||||
}));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (key.ctrl && input === 'l') {
|
if (key.ctrl && input === 'l') {
|
||||||
onClearScreen();
|
onClearScreen();
|
||||||
return true;
|
return true;
|
||||||
|
@ -193,7 +175,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||||
activeSuggestionIndex,
|
activeSuggestionIndex,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
inputHistory,
|
inputHistory,
|
||||||
setEditorState,
|
|
||||||
onClearScreen,
|
onClearScreen,
|
||||||
shellModeActive,
|
shellModeActive,
|
||||||
setShellModeActive,
|
setShellModeActive,
|
||||||
|
|
|
@ -44,12 +44,7 @@ export interface MultilineTextEditorProps {
|
||||||
|
|
||||||
// Called on all key events to allow the caller. Returns true if the
|
// Called on all key events to allow the caller. Returns true if the
|
||||||
// event was handled and should not be passed to the editor.
|
// event was handled and should not be passed to the editor.
|
||||||
readonly inputPreprocessor?: (
|
readonly inputPreprocessor?: (input: string, key: Key) => boolean;
|
||||||
input: string,
|
|
||||||
key: Key,
|
|
||||||
currentText: string,
|
|
||||||
cursorOffset: number,
|
|
||||||
) => boolean;
|
|
||||||
|
|
||||||
// Optional initial cursor position (character offset)
|
// Optional initial cursor position (character offset)
|
||||||
readonly initialCursorOffset?: number;
|
readonly initialCursorOffset?: number;
|
||||||
|
@ -98,14 +93,7 @@ export const MultilineTextEditor = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate cursorOffset for inputPreprocessor
|
if (inputPreprocessor?.(input, key) === true) {
|
||||||
let charOffset = 0;
|
|
||||||
for (let i = 0; i < buffer.cursor[0]; i++) {
|
|
||||||
charOffset += buffer.lines[i].length + 1; // +1 for newline
|
|
||||||
}
|
|
||||||
charOffset += buffer.cursor[1];
|
|
||||||
|
|
||||||
if (inputPreprocessor?.(input, key, buffer.text, charOffset) === true) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,14 +109,7 @@ export const MultilineTextEditor = ({
|
||||||
|
|
||||||
const isCtrlX =
|
const isCtrlX =
|
||||||
(key.ctrl && (input === 'x' || input === '\x18')) || input === '\x18';
|
(key.ctrl && (input === 'x' || input === '\x18')) || input === '\x18';
|
||||||
const isCtrlE =
|
if (isCtrlX) {
|
||||||
(key.ctrl && (input === 'e' || input === '\x05')) ||
|
|
||||||
input === '\x05' ||
|
|
||||||
(!key.ctrl &&
|
|
||||||
input === 'e' &&
|
|
||||||
input.length === 1 &&
|
|
||||||
input.charCodeAt(0) === 5);
|
|
||||||
if (isCtrlX || isCtrlE) {
|
|
||||||
buffer.openInExternalEditor();
|
buffer.openInExternalEditor();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1104,16 +1104,22 @@ export function useTextBuffer({
|
||||||
if (key['return'] || input === '\r' || input === '\n') newline();
|
if (key['return'] || input === '\r' || input === '\n') newline();
|
||||||
else if (key['leftArrow'] && !key['meta'] && !key['ctrl'] && !key['alt'])
|
else if (key['leftArrow'] && !key['meta'] && !key['ctrl'] && !key['alt'])
|
||||||
move('left');
|
move('left');
|
||||||
|
else if (key['ctrl'] && input === 'b') move('left');
|
||||||
else if (key['rightArrow'] && !key['meta'] && !key['ctrl'] && !key['alt'])
|
else if (key['rightArrow'] && !key['meta'] && !key['ctrl'] && !key['alt'])
|
||||||
move('right');
|
move('right');
|
||||||
|
else if (key['ctrl'] && input === 'f') move('right');
|
||||||
else if (key['upArrow']) move('up');
|
else if (key['upArrow']) move('up');
|
||||||
else if (key['downArrow']) move('down');
|
else if (key['downArrow']) move('down');
|
||||||
else if ((key['meta'] || key['ctrl'] || key['alt']) && key['leftArrow'])
|
else if ((key['ctrl'] || key['alt']) && key['leftArrow'])
|
||||||
move('wordLeft');
|
move('wordLeft');
|
||||||
else if ((key['meta'] || key['ctrl'] || key['alt']) && key['rightArrow'])
|
else if (key['meta'] && input === 'b') move('wordLeft');
|
||||||
|
else if ((key['ctrl'] || key['alt']) && key['rightArrow'])
|
||||||
move('wordRight');
|
move('wordRight');
|
||||||
|
else if (key['meta'] && input === 'f') move('wordRight');
|
||||||
else if (key['home']) move('home');
|
else if (key['home']) move('home');
|
||||||
|
else if (key['ctrl'] && input === 'a') move('home');
|
||||||
else if (key['end']) move('end');
|
else if (key['end']) move('end');
|
||||||
|
else if (key['ctrl'] && input === 'e') move('end');
|
||||||
else if (
|
else if (
|
||||||
(key['meta'] || key['ctrl'] || key['alt']) &&
|
(key['meta'] || key['ctrl'] || key['alt']) &&
|
||||||
(key['backspace'] || input === '\x7f')
|
(key['backspace'] || input === '\x7f')
|
||||||
|
|
Loading…
Reference in New Issue