summaryrefslogtreecommitdiff
path: root/packages/cli/src/ui/components/InputPrompt.tsx
diff options
context:
space:
mode:
authorDaniel Lee <[email protected]>2025-06-13 09:59:09 -0700
committerGitHub <[email protected]>2025-06-13 16:59:09 +0000
commitdaceb9963f8962051628127c661297fbd95b4a1d (patch)
tree5f528211ba48de4386df3aa3240042132782a88d /packages/cli/src/ui/components/InputPrompt.tsx
parent8e804c9fa1d529dee98caed48354791976ca6f6c (diff)
feat(cli): support ctrl+d to exit (#878)
Similar to ctrl+c, ctrl+d can now be used to exit the program. To avoid accidental exit, ctrl+d must be pressed twice in relatively quick succession (same as ctrl+c). Following common UX pattern, ctrl+d will be ignored when the input prompt is non-empty. This behavior is similar to how most shell (bash/zsh) behaves. To support this, I had to refactor so that text buffer is initialized outside of the InputPrompt component and instead do it on the main App component to allow input controller to have access to check the content of the text buffer.
Diffstat (limited to 'packages/cli/src/ui/components/InputPrompt.tsx')
-rw-r--r--packages/cli/src/ui/components/InputPrompt.tsx51
1 files changed, 12 insertions, 39 deletions
diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx
index 534d7112..c4177c00 100644
--- a/packages/cli/src/ui/components/InputPrompt.tsx
+++ b/packages/cli/src/ui/components/InputPrompt.tsx
@@ -4,15 +4,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import * as fs from 'fs';
import React, { useCallback, useEffect, useState } from 'react';
-import { Text, Box, useInput, useStdin } from 'ink';
+import { Text, Box, useInput } from 'ink';
import { Colors } from '../colors.js';
import { SuggestionsDisplay } from './SuggestionsDisplay.js';
import { useInputHistory } from '../hooks/useInputHistory.js';
-import { useTextBuffer, cpSlice, cpLen } from './shared/text-buffer.js';
+import { cpSlice, cpLen, TextBuffer } from './shared/text-buffer.js';
import chalk from 'chalk';
-import { useTerminalSize } from '../hooks/useTerminalSize.js';
import stringWidth from 'string-width';
import process from 'node:process';
import { useCompletion } from '../hooks/useCompletion.js';
@@ -21,60 +19,36 @@ import { SlashCommand } from '../hooks/slashCommandProcessor.js';
import { Config } from '@gemini-cli/core';
export interface InputPromptProps {
+ buffer: TextBuffer;
onSubmit: (value: string) => void;
userMessages: readonly string[];
onClearScreen: () => void;
config: Config; // Added config for useCompletion
slashCommands: SlashCommand[]; // Added slashCommands for useCompletion
placeholder?: string;
- height?: number; // Visible height of the editor area
focus?: boolean;
- widthFraction: number;
+ inputWidth: number;
+ suggestionsWidth: number;
shellModeActive: boolean;
setShellModeActive: (value: boolean) => void;
}
export const InputPrompt: React.FC<InputPromptProps> = ({
+ buffer,
onSubmit,
userMessages,
onClearScreen,
config,
slashCommands,
placeholder = ' Type your message or @path/to/file',
- height = 10,
focus = true,
- widthFraction,
+ inputWidth,
+ suggestionsWidth,
shellModeActive,
setShellModeActive,
}) => {
- const terminalSize = useTerminalSize();
- const padding = 3;
- const effectiveWidth = Math.max(
- 20,
- Math.round(terminalSize.columns * widthFraction) - padding,
- );
- const suggestionsWidth = Math.max(60, Math.floor(terminalSize.columns * 0.8));
-
const [justNavigatedHistory, setJustNavigatedHistory] = useState(false);
- const { stdin, setRawMode } = useStdin();
-
- const isValidPath = useCallback((filePath: string): boolean => {
- try {
- return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
- } catch (_e) {
- return false;
- }
- }, []);
-
- const buffer = useTextBuffer({
- initialText: '',
- viewport: { height, width: effectiveWidth },
- stdin,
- setRawMode,
- isValidPath,
- });
-
const completion = useCompletion(
buffer.text,
config.getTargetDir(),
@@ -370,11 +344,10 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
) : (
linesToRender.map((lineText, visualIdxInRenderedSet) => {
const cursorVisualRow = cursorVisualRowAbsolute - scrollVisualRow;
- let display = cpSlice(lineText, 0, effectiveWidth);
+ let display = cpSlice(lineText, 0, inputWidth);
const currentVisualWidth = stringWidth(display);
- if (currentVisualWidth < effectiveWidth) {
- display =
- display + ' '.repeat(effectiveWidth - currentVisualWidth);
+ if (currentVisualWidth < inputWidth) {
+ display = display + ' '.repeat(inputWidth - currentVisualWidth);
}
if (visualIdxInRenderedSet === cursorVisualRow) {
@@ -394,7 +367,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
cpSlice(display, relativeVisualColForHighlight + 1);
} else if (
relativeVisualColForHighlight === cpLen(display) &&
- cpLen(display) === effectiveWidth
+ cpLen(display) === inputWidth
) {
display = display + chalk.inverse(' ');
}