This commit is contained in:
Sijie Wang
2025-07-25 15:36:42 -07:00
committed by GitHub
parent aa71438684
commit fbdc8d5ab3
21 changed files with 5324 additions and 191 deletions

View File

@@ -73,6 +73,8 @@ import { useGitBranchName } from './hooks/useGitBranchName.js';
import { useFocus } from './hooks/useFocus.js';
import { useBracketedPaste } from './hooks/useBracketedPaste.js';
import { useTextBuffer } from './components/shared/text-buffer.js';
import { useVimMode, VimModeProvider } from './contexts/VimModeContext.js';
import { useVim } from './hooks/vim.js';
import * as fs from 'fs';
import { UpdateNotification } from './components/UpdateNotification.js';
import {
@@ -97,7 +99,9 @@ interface AppProps {
export const AppWrapper = (props: AppProps) => (
<SessionStatsProvider>
<App {...props} />
<VimModeProvider settings={props.settings}>
<App {...props} />
</VimModeProvider>
</SessionStatsProvider>
);
@@ -374,6 +378,49 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
config.setFlashFallbackHandler(flashFallbackHandler);
}, [config, addItem, userTier]);
// Terminal and UI setup
const { rows: terminalHeight, columns: terminalWidth } = useTerminalSize();
const { stdin, setRawMode } = useStdin();
const isInitialMount = useRef(true);
const widthFraction = 0.9;
const inputWidth = Math.max(
20,
Math.floor(terminalWidth * widthFraction) - 3,
);
const suggestionsWidth = Math.max(60, Math.floor(terminalWidth * 0.8));
// Utility callbacks
const isValidPath = useCallback((filePath: string): boolean => {
try {
return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
} catch (_e) {
return false;
}
}, []);
const getPreferredEditor = useCallback(() => {
const editorType = settings.merged.preferredEditor;
const isValidEditor = isEditorAvailable(editorType);
if (!isValidEditor) {
openEditorDialog();
return;
}
return editorType as EditorType;
}, [settings, openEditorDialog]);
const onAuthError = useCallback(() => {
setAuthError('reauth required');
openAuthDialog();
}, [openAuthDialog, setAuthError]);
// Core hooks and processors
const {
vimEnabled: vimModeEnabled,
vimMode,
toggleVimEnabled,
} = useVimMode();
const {
handleSlashCommand,
slashCommands,
@@ -394,26 +441,41 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
toggleCorgiMode,
setQuittingMessages,
openPrivacyNotice,
toggleVimEnabled,
);
const pendingHistoryItems = [...pendingSlashCommandHistoryItems];
const { rows: terminalHeight, columns: terminalWidth } = useTerminalSize();
const isInitialMount = useRef(true);
const { stdin, setRawMode } = useStdin();
const isValidPath = useCallback((filePath: string): boolean => {
try {
return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
} catch (_e) {
return false;
}
}, []);
const widthFraction = 0.9;
const inputWidth = Math.max(
20,
Math.floor(terminalWidth * widthFraction) - 3,
const {
streamingState,
submitQuery,
initError,
pendingHistoryItems: pendingGeminiHistoryItems,
thought,
} = useGeminiStream(
config.getGeminiClient(),
history,
addItem,
setShowHelp,
config,
setDebugMessage,
handleSlashCommand,
shellModeActive,
getPreferredEditor,
onAuthError,
performMemoryRefresh,
modelSwitchedFromQuotaError,
setModelSwitchedFromQuotaError,
);
// Input handling
const handleFinalSubmit = useCallback(
(submittedValue: string) => {
const trimmedValue = submittedValue.trim();
if (trimmedValue.length > 0) {
submitQuery(trimmedValue);
}
},
[submitQuery],
);
const suggestionsWidth = Math.max(60, Math.floor(terminalWidth * 0.8));
const buffer = useTextBuffer({
initialText: '',
@@ -424,6 +486,14 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
shellModeActive,
});
const { handleInput: vimHandleInput } = useVim(buffer, handleFinalSubmit);
const pendingHistoryItems = [...pendingSlashCommandHistoryItems];
pendingHistoryItems.push(...pendingGeminiHistoryItems);
const { elapsedTime, currentLoadingPhrase } =
useLoadingIndicator(streamingState);
const showAutoAcceptIndicator = useAutoAcceptIndicator({ config });
const handleExit = useCallback(
(
pressedOnce: boolean,
@@ -489,57 +559,6 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
}
}, [config]);
const getPreferredEditor = useCallback(() => {
const editorType = settings.merged.preferredEditor;
const isValidEditor = isEditorAvailable(editorType);
if (!isValidEditor) {
openEditorDialog();
return;
}
return editorType as EditorType;
}, [settings, openEditorDialog]);
const onAuthError = useCallback(() => {
setAuthError('reauth required');
openAuthDialog();
}, [openAuthDialog, setAuthError]);
const {
streamingState,
submitQuery,
initError,
pendingHistoryItems: pendingGeminiHistoryItems,
thought,
} = useGeminiStream(
config.getGeminiClient(),
history,
addItem,
setShowHelp,
config,
setDebugMessage,
handleSlashCommand,
shellModeActive,
getPreferredEditor,
onAuthError,
performMemoryRefresh,
modelSwitchedFromQuotaError,
setModelSwitchedFromQuotaError,
);
pendingHistoryItems.push(...pendingGeminiHistoryItems);
const { elapsedTime, currentLoadingPhrase } =
useLoadingIndicator(streamingState);
const showAutoAcceptIndicator = useAutoAcceptIndicator({ config });
const handleFinalSubmit = useCallback(
(submittedValue: string) => {
const trimmedValue = submittedValue.trim();
if (trimmedValue.length > 0) {
submitQuery(trimmedValue);
}
},
[submitQuery],
);
const logger = useLogger();
const [userMessages, setUserMessages] = useState<string[]>([]);
@@ -697,6 +716,10 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
// Arbitrary threshold to ensure that items in the static area are large
// enough but not too large to make the terminal hard to use.
const staticAreaMaxItemHeight = Math.max(terminalHeight * 4, 100);
const placeholder = vimModeEnabled
? " Press 'i' for INSERT mode and 'Esc' for NORMAL mode."
: ' Type your message or @path/to/file';
return (
<StreamingContext.Provider value={streamingState}>
<Box flexDirection="column" marginBottom={1} width="90%">
@@ -938,6 +961,8 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
shellModeActive={shellModeActive}
setShellModeActive={setShellModeActive}
focus={isFocused}
vimHandleInput={vimHandleInput}
placeholder={placeholder}
/>
)}
</>
@@ -989,6 +1014,7 @@ const App = ({ config, settings, startupWarnings = [], version }: AppProps) => {
}
promptTokenCount={sessionStats.lastPromptTokenCount}
nightly={nightly}
vimMode={vimModeEnabled ? vimMode : undefined}
/>
</Box>
</Box>