mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
Handle stdin for prompts using readline for escape character parsing (#1972)
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Text, Box, useInput } from 'ink';
|
||||
import { Box, Text } from 'ink';
|
||||
import { Colors } from '../colors.js';
|
||||
import { SuggestionsDisplay } from './SuggestionsDisplay.js';
|
||||
import { useInputHistory } from '../hooks/useInputHistory.js';
|
||||
@@ -16,6 +16,7 @@ import stringWidth from 'string-width';
|
||||
import process from 'node:process';
|
||||
import { useShellHistory } from '../hooks/useShellHistory.js';
|
||||
import { useCompletion } from '../hooks/useCompletion.js';
|
||||
import { useKeypress, Key } from '../hooks/useKeypress.js';
|
||||
import { isAtCommand, isSlashCommand } from '../utils/commandUtils.js';
|
||||
import { SlashCommand } from '../hooks/slashCommandProcessor.js';
|
||||
import { Config } from '@google/gemini-cli-core';
|
||||
@@ -155,29 +156,29 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
],
|
||||
);
|
||||
|
||||
useInput(
|
||||
(input, key) => {
|
||||
const handleInput = useCallback(
|
||||
(key: Key) => {
|
||||
if (!focus) {
|
||||
return;
|
||||
}
|
||||
const query = buffer.text;
|
||||
|
||||
if (input === '!' && query === '' && !completion.showSuggestions) {
|
||||
if (key.sequence === '!' && query === '' && !completion.showSuggestions) {
|
||||
setShellModeActive(!shellModeActive);
|
||||
buffer.setText(''); // Clear the '!' from input
|
||||
return true;
|
||||
}
|
||||
|
||||
if (completion.showSuggestions) {
|
||||
if (key.upArrow) {
|
||||
if (key.name === 'up') {
|
||||
completion.navigateUp();
|
||||
return;
|
||||
}
|
||||
if (key.downArrow) {
|
||||
if (key.name === 'down') {
|
||||
completion.navigateDown();
|
||||
return;
|
||||
}
|
||||
if (key.tab) {
|
||||
if (key.name === 'tab') {
|
||||
if (completion.suggestions.length > 0) {
|
||||
const targetIndex =
|
||||
completion.activeSuggestionIndex === -1
|
||||
@@ -189,7 +190,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (key.return) {
|
||||
if (key.name === 'return') {
|
||||
if (completion.activeSuggestionIndex >= 0) {
|
||||
handleAutocomplete(completion.activeSuggestionIndex);
|
||||
} else if (query.trim()) {
|
||||
@@ -199,19 +200,19 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
}
|
||||
} else {
|
||||
// Keybindings when suggestions are not shown
|
||||
if (key.ctrl && input === 'l') {
|
||||
if (key.ctrl && key.name === 'l') {
|
||||
onClearScreen();
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
if (key.ctrl && input === 'p') {
|
||||
if (key.ctrl && key.name === 'p') {
|
||||
inputHistory.navigateUp();
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
if (key.ctrl && input === 'n') {
|
||||
if (key.ctrl && key.name === 'n') {
|
||||
inputHistory.navigateDown();
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
if (key.escape) {
|
||||
if (key.name === 'escape') {
|
||||
if (shellModeActive) {
|
||||
setShellModeActive(false);
|
||||
return;
|
||||
@@ -222,54 +223,55 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
}
|
||||
|
||||
// Ctrl+A (Home)
|
||||
if (key.ctrl && input === 'a') {
|
||||
if (key.ctrl && key.name === 'a') {
|
||||
buffer.move('home');
|
||||
buffer.moveToOffset(0);
|
||||
return;
|
||||
}
|
||||
// Ctrl+E (End)
|
||||
if (key.ctrl && input === 'e') {
|
||||
if (key.ctrl && key.name === 'e') {
|
||||
buffer.move('end');
|
||||
buffer.moveToOffset(cpLen(buffer.text));
|
||||
return;
|
||||
}
|
||||
// Ctrl+L (Clear Screen)
|
||||
if (key.ctrl && input === 'l') {
|
||||
if (key.ctrl && key.name === 'l') {
|
||||
onClearScreen();
|
||||
return;
|
||||
}
|
||||
// Ctrl+P (History Up)
|
||||
if (key.ctrl && input === 'p' && !completion.showSuggestions) {
|
||||
if (key.ctrl && key.name === 'p' && !completion.showSuggestions) {
|
||||
inputHistory.navigateUp();
|
||||
return;
|
||||
}
|
||||
// Ctrl+N (History Down)
|
||||
if (key.ctrl && input === 'n' && !completion.showSuggestions) {
|
||||
if (key.ctrl && key.name === 'n' && !completion.showSuggestions) {
|
||||
inputHistory.navigateDown();
|
||||
return;
|
||||
}
|
||||
|
||||
// Core text editing from MultilineTextEditor's useInput
|
||||
if (key.ctrl && input === 'k') {
|
||||
if (key.ctrl && key.name === 'k') {
|
||||
buffer.killLineRight();
|
||||
return;
|
||||
}
|
||||
if (key.ctrl && input === 'u') {
|
||||
if (key.ctrl && key.name === 'u') {
|
||||
buffer.killLineLeft();
|
||||
return;
|
||||
}
|
||||
const isCtrlX =
|
||||
(key.ctrl && (input === 'x' || input === '\x18')) || input === '\x18';
|
||||
(key.ctrl && (key.name === 'x' || key.sequence === '\x18')) ||
|
||||
key.sequence === '\x18';
|
||||
const isCtrlEFromEditor =
|
||||
(key.ctrl && (input === 'e' || input === '\x05')) ||
|
||||
input === '\x05' ||
|
||||
(key.ctrl && (key.name === 'e' || key.sequence === '\x05')) ||
|
||||
key.sequence === '\x05' ||
|
||||
(!key.ctrl &&
|
||||
input === 'e' &&
|
||||
input.length === 1 &&
|
||||
input.charCodeAt(0) === 5);
|
||||
key.name === 'e' &&
|
||||
key.sequence.length === 1 &&
|
||||
key.sequence.charCodeAt(0) === 5);
|
||||
|
||||
if (isCtrlX || isCtrlEFromEditor) {
|
||||
if (isCtrlEFromEditor && !(key.ctrl && input === 'e')) {
|
||||
if (isCtrlEFromEditor && !(key.ctrl && key.name === 'e')) {
|
||||
// Avoid double handling Ctrl+E
|
||||
buffer.openInExternalEditor();
|
||||
return;
|
||||
@@ -284,16 +286,15 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
process.env['TEXTBUFFER_DEBUG'] === '1' ||
|
||||
process.env['TEXTBUFFER_DEBUG'] === 'true'
|
||||
) {
|
||||
console.log('[InputPromptCombined] event', { input, key });
|
||||
console.log('[InputPromptCombined] event', { key });
|
||||
}
|
||||
|
||||
// Ctrl+Enter for newline, Enter for submit
|
||||
if (key.return) {
|
||||
if (key.name === 'return') {
|
||||
const [row, col] = buffer.cursor;
|
||||
const line = buffer.lines[row];
|
||||
const charBefore = col > 0 ? cpSlice(line, col - 1, col) : '';
|
||||
|
||||
if (key.ctrl || charBefore === '\\') {
|
||||
if (key.ctrl || key.meta || charBefore === '\\' || key.paste) {
|
||||
// Ctrl+Enter or escaped newline
|
||||
if (charBefore === '\\') {
|
||||
buffer.backspace();
|
||||
@@ -309,7 +310,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
}
|
||||
|
||||
// Standard arrow navigation within the buffer
|
||||
if (key.upArrow && !completion.showSuggestions) {
|
||||
if (key.name === 'up' && !completion.showSuggestions) {
|
||||
if (shellModeActive) {
|
||||
const prevCommand = shellHistory.getPreviousCommand();
|
||||
if (prevCommand !== null) {
|
||||
@@ -328,7 +329,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (key.downArrow && !completion.showSuggestions) {
|
||||
if (key.name === 'down' && !completion.showSuggestions) {
|
||||
if (shellModeActive) {
|
||||
const nextCommand = shellHistory.getNextCommand();
|
||||
if (nextCommand !== null) {
|
||||
@@ -349,13 +350,24 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
}
|
||||
|
||||
// Fallback to buffer's default input handling
|
||||
buffer.handleInput(input, key as Record<string, boolean>);
|
||||
},
|
||||
{
|
||||
isActive: focus,
|
||||
buffer.handleInput(key);
|
||||
},
|
||||
[
|
||||
focus,
|
||||
buffer,
|
||||
completion,
|
||||
shellModeActive,
|
||||
setShellModeActive,
|
||||
onClearScreen,
|
||||
inputHistory,
|
||||
handleAutocomplete,
|
||||
handleSubmitAndClear,
|
||||
shellHistory,
|
||||
],
|
||||
);
|
||||
|
||||
useKeypress(handleInput, { isActive: focus });
|
||||
|
||||
const linesToRender = buffer.viewportVisualLines;
|
||||
const [cursorVisualRowAbsolute, cursorVisualColAbsolute] =
|
||||
buffer.visualCursor;
|
||||
|
||||
Reference in New Issue
Block a user