@shutootaki/gwm
Version:
git worktree manager CLI
67 lines • 4.54 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { Text, Box, useInput } from 'ink';
import { join } from 'path';
import { loadConfig } from '../config.js';
import { getRepositoryName } from '../utils/git.js';
import { useEditableText } from '../hooks/useEditableText.js';
export const TextInput = ({ title, placeholder, initialValue = '', onSubmit, onCancel, onModeSwitch, validate, preview, }) => {
// 共通フックで文字列編集ロジックを扱う
const { value, cursorPosition } = useEditableText({ initialValue });
const validationError = validate ? validate(value) : null;
const previewText = preview ? preview(value) : null;
const canSubmit = value.trim() && !validationError;
// 送信 / キャンセル / モード切替など、フック外のキーイベントのみ処理
useInput((input, key) => {
if (key.escape) {
onCancel();
return;
}
if (key.return) {
if (canSubmit) {
onSubmit(value.trim());
}
return;
}
if (key.tab && onModeSwitch) {
onModeSwitch();
return;
}
});
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "cyan", bold: true, children: title }) }), _jsx(Box, { marginBottom: 1, children: _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "gray", children: placeholder }), _jsxs(Box, { marginTop: 0, children: [_jsxs(Text, { color: "cyan", bold: true, children: ["\u276F", ' '] }), _jsx(Text, { color: validationError ? 'red' : 'white', children: value.slice(0, cursorPosition) }), _jsx(Text, { color: "cyan", children: "\u2588" }), _jsx(Text, { color: validationError ? 'red' : 'white', children: value.slice(cursorPosition) })] })] }) }), validationError && (_jsx(Box, { marginBottom: 1, children: _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "red", padding: 1, children: [_jsx(Text, { color: "red", bold: true, children: "Invalid input" }), _jsx(Text, { color: "red", children: validationError })] }) })), previewText && !validationError && value.trim() && (_jsx(Box, { marginBottom: 1, children: _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "green", padding: 1, children: [_jsx(Text, { color: "green", bold: true, children: "Preview" }), _jsx(Text, { color: "gray", children: "Worktree will be created at:" }), _jsx(Text, { color: "cyan", children: previewText })] }) })), _jsx(Box, { children: _jsxs(Text, { color: "gray", children: [_jsx(Text, { color: "green", children: "Enter" }), " ", canSubmit ? 'create' : 'disabled', " \u2022", ' ', _jsx(Text, { color: "red", children: "Esc" }), " cancel", onModeSwitch && (_jsxs(_Fragment, { children: [' ', "\u2022 ", _jsx(Text, { color: "yellow", children: "Tab" }), " browse remote branches"] })), ' ', "\u2022 ", _jsx(Text, { color: "yellow", children: "Cmd+Del" }), " clear \u2022", ' ', _jsx(Text, { color: "yellow", children: "Ctrl+W/\u2325\u232B" }), " delete-word"] }) })] }));
};
// ブランチ名用のバリデーション関数
export function validateBranchName(branchName) {
if (!branchName.trim()) {
return 'Branch name cannot be empty';
}
// Git ブランチ名に使用できない文字をチェック。
// 文字クラス内で '[' ']' '\\' を表現するため、それぞれ \\[ \\] \\\\ とエスケープする。
const invalidChars = /[~^:?*\\[\]]/;
if (invalidChars.test(branchName)) {
return 'Branch name contains invalid characters (~^:?*[]\\)';
}
if (branchName.startsWith('.') || branchName.endsWith('.')) {
return 'Branch name cannot start or end with a dot';
}
if (branchName.includes('..')) {
return 'Branch name cannot contain consecutive dots';
}
if (branchName.includes(' ')) {
return 'Branch name cannot contain spaces';
}
if (branchName.length > 50) {
return 'Branch name is too long (max 50 characters)';
}
return null;
}
// ワークツリーパスのプレビュー生成関数
export function generateWorktreePreview(branchName) {
if (!branchName.trim()) {
return null;
}
const config = loadConfig();
const repoName = getRepositoryName();
const sanitizedBranch = branchName.replace(/\//g, '-');
return join(config.worktree_base_path, repoName, sanitizedBranch);
}
//# sourceMappingURL=TextInput.js.map