mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-22 02:54:31 -07:00
refactor(cli): better react patterns for BaseSettingsDialog (#21206)
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { useReducer, useCallback, useEffect, useState } from 'react';
|
||||
import { cpSlice, cpLen, stripUnsafeCharacters } from '../utils/textUtils.js';
|
||||
|
||||
export interface EditBufferState {
|
||||
editingKey: string | null;
|
||||
buffer: string;
|
||||
cursorPos: number;
|
||||
}
|
||||
|
||||
export type EditBufferAction =
|
||||
| { type: 'START_EDIT'; key: string; initialValue: string }
|
||||
| { type: 'COMMIT_EDIT' }
|
||||
| { type: 'MOVE_LEFT' }
|
||||
| { type: 'MOVE_RIGHT' }
|
||||
| { type: 'HOME' }
|
||||
| { type: 'END' }
|
||||
| { type: 'DELETE_LEFT' }
|
||||
| { type: 'DELETE_RIGHT' }
|
||||
| { type: 'INSERT_CHAR'; char: string; isNumberType: boolean };
|
||||
|
||||
const initialState: EditBufferState = {
|
||||
editingKey: null,
|
||||
buffer: '',
|
||||
cursorPos: 0,
|
||||
};
|
||||
|
||||
function editBufferReducer(
|
||||
state: EditBufferState,
|
||||
action: EditBufferAction,
|
||||
): EditBufferState {
|
||||
switch (action.type) {
|
||||
case 'START_EDIT':
|
||||
return {
|
||||
editingKey: action.key,
|
||||
buffer: action.initialValue,
|
||||
cursorPos: cpLen(action.initialValue),
|
||||
};
|
||||
|
||||
case 'COMMIT_EDIT':
|
||||
return initialState;
|
||||
|
||||
case 'MOVE_LEFT':
|
||||
return {
|
||||
...state,
|
||||
cursorPos: Math.max(0, state.cursorPos - 1),
|
||||
};
|
||||
|
||||
case 'MOVE_RIGHT':
|
||||
return {
|
||||
...state,
|
||||
cursorPos: Math.min(cpLen(state.buffer), state.cursorPos + 1),
|
||||
};
|
||||
|
||||
case 'HOME':
|
||||
return { ...state, cursorPos: 0 };
|
||||
|
||||
case 'END':
|
||||
return { ...state, cursorPos: cpLen(state.buffer) };
|
||||
|
||||
case 'DELETE_LEFT': {
|
||||
if (state.cursorPos === 0) return state;
|
||||
const before = cpSlice(state.buffer, 0, state.cursorPos - 1);
|
||||
const after = cpSlice(state.buffer, state.cursorPos);
|
||||
return {
|
||||
...state,
|
||||
buffer: before + after,
|
||||
cursorPos: state.cursorPos - 1,
|
||||
};
|
||||
}
|
||||
|
||||
case 'DELETE_RIGHT': {
|
||||
if (state.cursorPos === cpLen(state.buffer)) return state;
|
||||
const before = cpSlice(state.buffer, 0, state.cursorPos);
|
||||
const after = cpSlice(state.buffer, state.cursorPos + 1);
|
||||
return {
|
||||
...state,
|
||||
buffer: before + after,
|
||||
};
|
||||
}
|
||||
|
||||
case 'INSERT_CHAR': {
|
||||
let ch = action.char;
|
||||
let isValidChar = false;
|
||||
|
||||
if (action.isNumberType) {
|
||||
isValidChar = /[0-9\-+.]/.test(ch);
|
||||
} else {
|
||||
isValidChar = ch.length === 1 && ch.charCodeAt(0) >= 32;
|
||||
ch = stripUnsafeCharacters(ch);
|
||||
}
|
||||
|
||||
if (!isValidChar || ch.length === 0) return state;
|
||||
|
||||
const before = cpSlice(state.buffer, 0, state.cursorPos);
|
||||
const after = cpSlice(state.buffer, state.cursorPos);
|
||||
return {
|
||||
...state,
|
||||
buffer: before + ch + after,
|
||||
cursorPos: state.cursorPos + 1,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export interface UseEditBufferProps {
|
||||
onCommit: (key: string, value: string) => void;
|
||||
}
|
||||
|
||||
export function useInlineEditBuffer({ onCommit }: UseEditBufferProps) {
|
||||
const [state, dispatch] = useReducer(editBufferReducer, initialState);
|
||||
const [cursorVisible, setCursorVisible] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.editingKey) {
|
||||
setCursorVisible(true);
|
||||
return;
|
||||
}
|
||||
setCursorVisible(true);
|
||||
const interval = setInterval(() => {
|
||||
setCursorVisible((v) => !v);
|
||||
}, 500);
|
||||
return () => clearInterval(interval);
|
||||
}, [state.editingKey, state.buffer, state.cursorPos]);
|
||||
|
||||
const startEditing = useCallback((key: string, initialValue: string) => {
|
||||
dispatch({ type: 'START_EDIT', key, initialValue });
|
||||
}, []);
|
||||
|
||||
const commitEdit = useCallback(() => {
|
||||
if (state.editingKey) {
|
||||
onCommit(state.editingKey, state.buffer);
|
||||
}
|
||||
dispatch({ type: 'COMMIT_EDIT' });
|
||||
}, [state.editingKey, state.buffer, onCommit]);
|
||||
|
||||
return {
|
||||
editState: state,
|
||||
editDispatch: dispatch,
|
||||
startEditing,
|
||||
commitEdit,
|
||||
cursorVisible,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user