ccmanager 1.4.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ConfigureCommand.js +27 -22
- package/dist/components/ConfigureHooks.js +2 -2
- package/dist/components/ConfigureWorktree.js +2 -2
- package/dist/components/DeleteConfirmation.js +70 -85
- package/dist/components/DeleteWorktree.js +32 -50
- package/dist/components/NewWorktree.js +4 -4
- package/dist/components/TextInputWrapper.d.ts +13 -0
- package/dist/components/TextInputWrapper.js +15 -0
- package/dist/utils/worktreeUtils.js +1 -2
- package/package.json +3 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
|
-
import
|
|
3
|
+
import TextInputWrapper from './TextInputWrapper.js';
|
|
4
4
|
import SelectInput from 'ink-select-input';
|
|
5
5
|
import { configurationManager } from '../services/configurationManager.js';
|
|
6
6
|
import { shortcutManager } from '../services/shortcutManager.js';
|
|
@@ -246,18 +246,12 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
246
246
|
// In input mode, let TextInput or SelectInput handle it
|
|
247
247
|
return;
|
|
248
248
|
}
|
|
249
|
-
if (viewMode === 'list' ||
|
|
249
|
+
if (viewMode === 'list' ||
|
|
250
|
+
viewMode === 'edit' ||
|
|
251
|
+
viewMode === 'delete-confirm') {
|
|
250
252
|
// SelectInput handles navigation and selection
|
|
251
253
|
return;
|
|
252
254
|
}
|
|
253
|
-
else if (viewMode === 'delete-confirm') {
|
|
254
|
-
if (key.upArrow || key.downArrow) {
|
|
255
|
-
setSelectedIndex(prev => (prev === 0 ? 1 : 0));
|
|
256
|
-
}
|
|
257
|
-
else if (key.return) {
|
|
258
|
-
handleDeleteConfirm();
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
255
|
});
|
|
262
256
|
// Render strategy selection
|
|
263
257
|
if (isSelectingStrategy) {
|
|
@@ -300,7 +294,7 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
300
294
|
errorMessage && (React.createElement(Box, { marginBottom: 1 },
|
|
301
295
|
React.createElement(Text, { color: "red" }, errorMessage))),
|
|
302
296
|
React.createElement(Box, null,
|
|
303
|
-
React.createElement(
|
|
297
|
+
React.createElement(TextInputWrapper, { value: inputValue, onChange: setInputValue, onSubmit: handleFieldUpdate, placeholder: editField === 'args' || editField === 'fallbackArgs'
|
|
304
298
|
? 'e.g., --resume or leave empty'
|
|
305
299
|
: '' })),
|
|
306
300
|
React.createElement(Box, { marginTop: 1 },
|
|
@@ -345,7 +339,7 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
345
339
|
errorMessage && (React.createElement(Box, { marginBottom: 1 },
|
|
346
340
|
React.createElement(Text, { color: "red" }, errorMessage))),
|
|
347
341
|
React.createElement(Box, null,
|
|
348
|
-
React.createElement(
|
|
342
|
+
React.createElement(TextInputWrapper, { value: inputValue, onChange: setInputValue, onSubmit: handleAddPresetInput, placeholder: addStep === 'args' || addStep === 'fallbackArgs'
|
|
349
343
|
? 'e.g., --resume or leave empty'
|
|
350
344
|
: addStep === 'name'
|
|
351
345
|
? 'e.g., Development'
|
|
@@ -360,6 +354,19 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
360
354
|
// Render delete confirmation
|
|
361
355
|
if (viewMode === 'delete-confirm') {
|
|
362
356
|
const preset = presets.find(p => p.id === selectedPresetId);
|
|
357
|
+
const confirmItems = [
|
|
358
|
+
{ label: 'Yes, delete', value: 'yes' },
|
|
359
|
+
{ label: 'Cancel', value: 'cancel' },
|
|
360
|
+
];
|
|
361
|
+
const handleConfirmSelect = (item) => {
|
|
362
|
+
if (item.value === 'yes') {
|
|
363
|
+
handleDeleteConfirm();
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
setViewMode('edit');
|
|
367
|
+
setSelectedIndex(6); // Return to delete option in edit menu
|
|
368
|
+
}
|
|
369
|
+
};
|
|
363
370
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
364
371
|
React.createElement(Box, { marginBottom: 1 },
|
|
365
372
|
React.createElement(Text, { bold: true, color: "red" }, "Confirm Delete")),
|
|
@@ -368,17 +375,15 @@ const ConfigureCommand = ({ onComplete }) => {
|
|
|
368
375
|
"Delete preset \"",
|
|
369
376
|
preset?.name,
|
|
370
377
|
"\"?")),
|
|
371
|
-
React.createElement(
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
selectedIndex === 1 ? '> ' : ' ',
|
|
379
|
-
"Cancel"))),
|
|
378
|
+
React.createElement(SelectInput, { items: confirmItems, onSelect: handleConfirmSelect, initialIndex: 1, indicatorComponent: ({ isSelected }) => (React.createElement(Text, { color: isSelected ? 'red' : undefined }, isSelected ? '>' : ' ')), itemComponent: ({ isSelected, label }) => (React.createElement(Text, { color: label === 'Yes, delete'
|
|
379
|
+
? isSelected
|
|
380
|
+
? 'red'
|
|
381
|
+
: undefined
|
|
382
|
+
: isSelected
|
|
383
|
+
? 'cyan'
|
|
384
|
+
: undefined, inverse: isSelected }, label)) }),
|
|
380
385
|
React.createElement(Box, { marginTop: 1 },
|
|
381
|
-
React.createElement(Text, { dimColor: true }, "Press \u2191\u2193 to navigate, Enter to confirm"))));
|
|
386
|
+
React.createElement(Text, { dimColor: true }, "Press \u2191\u2193/j/k to navigate, Enter to confirm"))));
|
|
382
387
|
}
|
|
383
388
|
// Render edit preset view
|
|
384
389
|
if (viewMode === 'edit') {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
|
-
import
|
|
3
|
+
import TextInputWrapper from './TextInputWrapper.js';
|
|
4
4
|
import SelectInput from 'ink-select-input';
|
|
5
5
|
import { configurationManager } from '../services/configurationManager.js';
|
|
6
6
|
const STATUS_LABELS = {
|
|
@@ -108,7 +108,7 @@ const ConfigureHooks = ({ onComplete }) => {
|
|
|
108
108
|
STATUS_LABELS[selectedStatus],
|
|
109
109
|
":")),
|
|
110
110
|
React.createElement(Box, { marginBottom: 1 },
|
|
111
|
-
React.createElement(
|
|
111
|
+
React.createElement(TextInputWrapper, { value: currentCommand, onChange: setCurrentCommand, onSubmit: handleCommandSubmit, placeholder: "Enter command (e.g., notify-send 'Claude is idle')" })),
|
|
112
112
|
React.createElement(Box, { marginBottom: 1 },
|
|
113
113
|
React.createElement(Text, null,
|
|
114
114
|
"Enabled: ",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
3
|
import SelectInput from 'ink-select-input';
|
|
4
|
-
import
|
|
4
|
+
import TextInputWrapper from './TextInputWrapper.js';
|
|
5
5
|
import { configurationManager } from '../services/configurationManager.js';
|
|
6
6
|
import { shortcutManager } from '../services/shortcutManager.js';
|
|
7
7
|
const ConfigureWorktree = ({ onComplete }) => {
|
|
@@ -84,7 +84,7 @@ const ConfigureWorktree = ({ onComplete }) => {
|
|
|
84
84
|
" - full branch name")),
|
|
85
85
|
React.createElement(Box, null,
|
|
86
86
|
React.createElement(Text, { color: "cyan" }, '> '),
|
|
87
|
-
React.createElement(
|
|
87
|
+
React.createElement(TextInputWrapper, { value: tempPattern, onChange: setTempPattern, onSubmit: handlePatternSubmit, placeholder: "../{branch}" })),
|
|
88
88
|
React.createElement(Box, { marginTop: 1 },
|
|
89
89
|
React.createElement(Text, { dimColor: true }, "Press Enter to save or Escape to cancel"))));
|
|
90
90
|
}
|
|
@@ -1,81 +1,61 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import SelectInput from 'ink-select-input';
|
|
3
4
|
import { shortcutManager } from '../services/shortcutManager.js';
|
|
4
5
|
const DeleteConfirmation = ({ worktrees, onConfirm, onCancel, }) => {
|
|
5
6
|
// Check if any worktrees have branches
|
|
6
7
|
const hasAnyBranches = worktrees.some(wt => wt.branch);
|
|
7
8
|
const [deleteBranch, setDeleteBranch] = useState(true);
|
|
8
|
-
const [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (focusedOption === 'confirm')
|
|
30
|
-
setFocusedOption('cancel');
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
const navigationMap = {
|
|
34
|
-
deleteBranch: 'keepBranch',
|
|
35
|
-
keepBranch: 'confirm',
|
|
36
|
-
confirm: 'cancel',
|
|
37
|
-
};
|
|
38
|
-
const next = navigationMap[focusedOption];
|
|
39
|
-
if (next)
|
|
40
|
-
setFocusedOption(next);
|
|
41
|
-
};
|
|
42
|
-
const handleHorizontalArrow = (direction) => {
|
|
43
|
-
if (isActionButton(focusedOption)) {
|
|
44
|
-
setFocusedOption(direction === 'left' ? 'confirm' : 'cancel');
|
|
45
|
-
}
|
|
9
|
+
const [view, setView] = useState(hasAnyBranches ? 'options' : 'confirm');
|
|
10
|
+
const [focusedOption, setFocusedOption] = useState(deleteBranch ? 'deleteBranch' : 'keepBranch');
|
|
11
|
+
// Menu items for branch options
|
|
12
|
+
const branchOptions = [
|
|
13
|
+
{
|
|
14
|
+
label: `${deleteBranch ? '(•)' : '( )'} Delete the branches too`,
|
|
15
|
+
value: 'deleteBranch',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
label: `${!deleteBranch ? '(•)' : '( )'} Keep the branches`,
|
|
19
|
+
value: 'keepBranch',
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
// Menu items for actions
|
|
23
|
+
const actionOptions = [
|
|
24
|
+
{ label: 'Confirm', value: 'confirm' },
|
|
25
|
+
{ label: 'Cancel', value: 'cancel' },
|
|
26
|
+
];
|
|
27
|
+
const handleBranchSelect = (item) => {
|
|
28
|
+
// Don't toggle on Enter - only update focused option
|
|
29
|
+
setFocusedOption(item.value);
|
|
46
30
|
};
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
setDeleteBranch(focusedOption === 'deleteBranch');
|
|
50
|
-
}
|
|
51
|
-
else if (focusedOption === 'confirm') {
|
|
31
|
+
const handleActionSelect = (item) => {
|
|
32
|
+
if (item.value === 'confirm') {
|
|
52
33
|
onConfirm(deleteBranch);
|
|
53
34
|
}
|
|
54
|
-
else
|
|
35
|
+
else {
|
|
55
36
|
onCancel();
|
|
56
37
|
}
|
|
57
38
|
};
|
|
58
39
|
useInput((input, key) => {
|
|
59
|
-
if (key
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
else if (key.downArrow) {
|
|
63
|
-
handleDownArrow();
|
|
64
|
-
}
|
|
65
|
-
else if (key.leftArrow) {
|
|
66
|
-
handleHorizontalArrow('left');
|
|
67
|
-
}
|
|
68
|
-
else if (key.rightArrow) {
|
|
69
|
-
handleHorizontalArrow('right');
|
|
40
|
+
if (shortcutManager.matchesShortcut('cancel', input, key)) {
|
|
41
|
+
onCancel();
|
|
70
42
|
}
|
|
71
|
-
else if (
|
|
72
|
-
|
|
43
|
+
else if (hasAnyBranches && view === 'options' && key.return) {
|
|
44
|
+
// Move to confirm view when Enter is pressed in options
|
|
45
|
+
setView('confirm');
|
|
73
46
|
}
|
|
74
|
-
else if (key.
|
|
75
|
-
|
|
47
|
+
else if (hasAnyBranches && view === 'confirm' && key.escape) {
|
|
48
|
+
// Go back to options when Escape is pressed in confirm
|
|
49
|
+
setView('options');
|
|
76
50
|
}
|
|
77
|
-
else if (
|
|
78
|
-
|
|
51
|
+
else if (hasAnyBranches && view === 'options' && input === ' ') {
|
|
52
|
+
// Toggle selection on space for radio buttons
|
|
53
|
+
if (focusedOption === 'deleteBranch') {
|
|
54
|
+
setDeleteBranch(true);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
setDeleteBranch(false);
|
|
58
|
+
}
|
|
79
59
|
}
|
|
80
60
|
});
|
|
81
61
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
@@ -101,33 +81,38 @@ const DeleteConfirmation = ({ worktrees, onConfirm, onCancel, }) => {
|
|
|
101
81
|
"... and ",
|
|
102
82
|
worktrees.length - 8,
|
|
103
83
|
" more worktrees")))),
|
|
104
|
-
hasAnyBranches && (React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
|
|
84
|
+
hasAnyBranches && view === 'options' && (React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
|
|
105
85
|
React.createElement(Text, { bold: true }, "What do you want to do with the associated branches?"),
|
|
106
|
-
React.createElement(Box, { marginTop: 1
|
|
107
|
-
React.createElement(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
React.createElement(Box, null,
|
|
122
|
-
React.createElement(Text, { color: focusedOption === 'cancel' ? 'red' : 'white', inverse: focusedOption === 'cancel' },
|
|
123
|
-
' ',
|
|
124
|
-
"Cancel",
|
|
125
|
-
' '))),
|
|
86
|
+
React.createElement(Box, { marginTop: 1 },
|
|
87
|
+
React.createElement(SelectInput, { items: branchOptions, onSelect: handleBranchSelect, onHighlight: (item) => {
|
|
88
|
+
setFocusedOption(item.value);
|
|
89
|
+
}, initialIndex: deleteBranch ? 0 : 1, indicatorComponent: ({ isSelected }) => (React.createElement(Text, { color: isSelected ? 'red' : undefined }, isSelected ? '>' : ' ')), itemComponent: ({ isSelected, label }) => (React.createElement(Text, { color: isSelected ? 'red' : undefined, inverse: isSelected }, label)) })))),
|
|
90
|
+
hasAnyBranches && view === 'confirm' && (React.createElement(Box, { marginBottom: 1, flexDirection: "column" },
|
|
91
|
+
React.createElement(Text, { bold: true }, "Branch option selected:"),
|
|
92
|
+
React.createElement(Text, { color: "yellow" }, deleteBranch ? '✓ Delete the branches too' : '✓ Keep the branches'))),
|
|
93
|
+
(view === 'confirm' || !hasAnyBranches) && (React.createElement(Box, { marginTop: 1 },
|
|
94
|
+
React.createElement(SelectInput, { items: actionOptions, onSelect: handleActionSelect, initialIndex: 1, indicatorComponent: ({ isSelected }) => (React.createElement(Text, null, isSelected ? '>' : ' ')), itemComponent: ({ isSelected, label }) => {
|
|
95
|
+
const color = label === 'Confirm' ? 'green' : 'red';
|
|
96
|
+
return (React.createElement(Text, { color: isSelected ? color : 'white', inverse: isSelected },
|
|
97
|
+
' ',
|
|
98
|
+
label,
|
|
99
|
+
' '));
|
|
100
|
+
} }))),
|
|
126
101
|
React.createElement(Box, { marginTop: 1 },
|
|
127
|
-
React.createElement(Text, { dimColor: true },
|
|
128
|
-
"Use \u2191\u2193 to navigate
|
|
102
|
+
React.createElement(Text, { dimColor: true }, hasAnyBranches && view === 'options' ? (React.createElement(React.Fragment, null,
|
|
103
|
+
"Use \u2191\u2193/j/k to navigate, Space to toggle, Enter to continue,",
|
|
104
|
+
' ',
|
|
105
|
+
shortcutManager.getShortcutDisplay('cancel'),
|
|
106
|
+
" to cancel")) : view === 'confirm' ? (React.createElement(React.Fragment, null,
|
|
107
|
+
"Use \u2191\u2193/j/k to navigate, Enter to select",
|
|
108
|
+
hasAnyBranches ? ', Esc to go back' : '',
|
|
109
|
+
",",
|
|
110
|
+
' ',
|
|
111
|
+
shortcutManager.getShortcutDisplay('cancel'),
|
|
112
|
+
" to cancel")) : (React.createElement(React.Fragment, null,
|
|
113
|
+
"Use \u2191\u2193/j/k to navigate, Enter to select,",
|
|
129
114
|
' ',
|
|
130
115
|
shortcutManager.getShortcutDisplay('cancel'),
|
|
131
|
-
" to cancel"))));
|
|
116
|
+
" to cancel"))))));
|
|
132
117
|
};
|
|
133
118
|
export default DeleteConfirmation;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import SelectInput from 'ink-select-input';
|
|
3
4
|
import { WorktreeService } from '../services/worktreeService.js';
|
|
4
5
|
import DeleteConfirmation from './DeleteConfirmation.js';
|
|
5
6
|
import { shortcutManager } from '../services/shortcutManager.js';
|
|
6
7
|
const DeleteWorktree = ({ onComplete, onCancel, }) => {
|
|
7
8
|
const [worktrees, setWorktrees] = useState([]);
|
|
8
9
|
const [selectedIndices, setSelectedIndices] = useState(new Set());
|
|
9
|
-
const [focusedIndex, setFocusedIndex] = useState(0);
|
|
10
10
|
const [confirmMode, setConfirmMode] = useState(false);
|
|
11
|
-
const
|
|
11
|
+
const [focusedIndex, setFocusedIndex] = useState(0);
|
|
12
12
|
useEffect(() => {
|
|
13
13
|
const worktreeService = new WorktreeService();
|
|
14
14
|
const allWorktrees = worktreeService.getWorktrees();
|
|
@@ -16,23 +16,30 @@ const DeleteWorktree = ({ onComplete, onCancel, }) => {
|
|
|
16
16
|
const deletableWorktrees = allWorktrees.filter(wt => !wt.isMainWorktree);
|
|
17
17
|
setWorktrees(deletableWorktrees);
|
|
18
18
|
}, []);
|
|
19
|
+
// Create menu items from worktrees
|
|
20
|
+
const menuItems = worktrees.map((worktree, index) => {
|
|
21
|
+
const branchName = worktree.branch
|
|
22
|
+
? worktree.branch.replace('refs/heads/', '')
|
|
23
|
+
: 'detached';
|
|
24
|
+
const isSelected = selectedIndices.has(index);
|
|
25
|
+
return {
|
|
26
|
+
label: `${isSelected ? '[✓]' : '[ ]'} ${branchName} (${worktree.path})`,
|
|
27
|
+
value: index.toString(),
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
const handleSelect = (item) => {
|
|
31
|
+
// Don't toggle on Enter - this will be used to confirm
|
|
32
|
+
// We'll handle Space key separately for toggling
|
|
33
|
+
const index = parseInt(item.value, 10);
|
|
34
|
+
setFocusedIndex(index);
|
|
35
|
+
};
|
|
19
36
|
useInput((input, key) => {
|
|
20
|
-
if (key.ctrl && input === 'c') {
|
|
21
|
-
onCancel();
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
37
|
if (confirmMode) {
|
|
25
38
|
// Confirmation component handles input
|
|
26
39
|
return;
|
|
27
40
|
}
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
else if (key.downArrow) {
|
|
32
|
-
setFocusedIndex(prev => Math.min(worktrees.length - 1, prev + 1));
|
|
33
|
-
}
|
|
34
|
-
else if (input === ' ') {
|
|
35
|
-
// Toggle selection
|
|
41
|
+
if (input === ' ') {
|
|
42
|
+
// Toggle selection on space
|
|
36
43
|
setSelectedIndices(prev => {
|
|
37
44
|
const newSet = new Set(prev);
|
|
38
45
|
if (newSet.has(focusedIndex)) {
|
|
@@ -44,10 +51,8 @@ const DeleteWorktree = ({ onComplete, onCancel, }) => {
|
|
|
44
51
|
return newSet;
|
|
45
52
|
});
|
|
46
53
|
}
|
|
47
|
-
else if (key.return) {
|
|
48
|
-
|
|
49
|
-
setConfirmMode(true);
|
|
50
|
-
}
|
|
54
|
+
else if (key.return && selectedIndices.size > 0) {
|
|
55
|
+
setConfirmMode(true);
|
|
51
56
|
}
|
|
52
57
|
else if (shortcutManager.matchesShortcut('cancel', input, key)) {
|
|
53
58
|
onCancel();
|
|
@@ -77,40 +82,17 @@ const DeleteWorktree = ({ onComplete, onCancel, }) => {
|
|
|
77
82
|
React.createElement(Text, { bold: true, color: "red" }, "Delete Worktrees")),
|
|
78
83
|
React.createElement(Box, { marginBottom: 1 },
|
|
79
84
|
React.createElement(Text, { dimColor: true }, "Select worktrees to delete (Space to select, Enter to confirm):")),
|
|
80
|
-
(() => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
viewportStart,
|
|
89
|
-
" more...")),
|
|
90
|
-
visibleWorktrees.map((worktree, relativeIndex) => {
|
|
91
|
-
const actualIndex = viewportStart + relativeIndex;
|
|
92
|
-
const isSelected = selectedIndices.has(actualIndex);
|
|
93
|
-
const isFocused = actualIndex === focusedIndex;
|
|
94
|
-
const branchName = worktree.branch
|
|
95
|
-
? worktree.branch.replace('refs/heads/', '')
|
|
96
|
-
: 'detached';
|
|
97
|
-
return (React.createElement(Box, { key: worktree.path },
|
|
98
|
-
React.createElement(Text, { color: isFocused ? 'green' : undefined, inverse: isFocused, dimColor: !isFocused && !isSelected },
|
|
99
|
-
isSelected ? '[✓]' : '[ ]',
|
|
100
|
-
" ",
|
|
101
|
-
branchName,
|
|
102
|
-
" (",
|
|
103
|
-
worktree.path,
|
|
104
|
-
")")));
|
|
105
|
-
}),
|
|
106
|
-
viewportEnd < worktrees.length && (React.createElement(Text, { dimColor: true },
|
|
107
|
-
"\u2193 ",
|
|
108
|
-
worktrees.length - viewportEnd,
|
|
109
|
-
" more..."))));
|
|
110
|
-
})(),
|
|
85
|
+
React.createElement(SelectInput, { items: menuItems, onSelect: handleSelect, onHighlight: (item) => {
|
|
86
|
+
const index = parseInt(item.value, 10);
|
|
87
|
+
setFocusedIndex(index);
|
|
88
|
+
}, limit: 10, indicatorComponent: ({ isSelected }) => (React.createElement(Text, { color: isSelected ? 'green' : undefined }, isSelected ? '>' : ' ')), itemComponent: ({ isSelected, label }) => {
|
|
89
|
+
// Check if this item is actually selected (checkbox checked)
|
|
90
|
+
const hasCheckmark = label.includes('[✓]');
|
|
91
|
+
return (React.createElement(Text, { color: isSelected ? 'green' : undefined, inverse: isSelected, dimColor: !isSelected && !hasCheckmark }, label));
|
|
92
|
+
} }),
|
|
111
93
|
React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
112
94
|
React.createElement(Text, { dimColor: true },
|
|
113
|
-
"Controls: \u2191\u2193 Navigate, Space Select, Enter Confirm,",
|
|
95
|
+
"Controls: \u2191\u2193/j/k Navigate, Space Select, Enter Confirm,",
|
|
114
96
|
' ',
|
|
115
97
|
shortcutManager.getShortcutDisplay('cancel'),
|
|
116
98
|
" Cancel"),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useMemo } from 'react';
|
|
2
2
|
import { Box, Text, useInput } from 'ink';
|
|
3
|
-
import
|
|
3
|
+
import TextInputWrapper from './TextInputWrapper.js';
|
|
4
4
|
import SelectInput from 'ink-select-input';
|
|
5
5
|
import { shortcutManager } from '../services/shortcutManager.js';
|
|
6
6
|
import { configurationManager } from '../services/configurationManager.js';
|
|
@@ -84,7 +84,7 @@ const NewWorktree = ({ onComplete, onCancel }) => {
|
|
|
84
84
|
React.createElement(Text, null, "Enter worktree path (relative to repository root):")),
|
|
85
85
|
React.createElement(Box, null,
|
|
86
86
|
React.createElement(Text, { color: "cyan" }, '> '),
|
|
87
|
-
React.createElement(
|
|
87
|
+
React.createElement(TextInputWrapper, { value: path, onChange: setPath, onSubmit: handlePathSubmit, placeholder: "e.g., ../myproject-feature" })))) : step === 'branch' && !isAutoDirectory ? (React.createElement(Box, { flexDirection: "column" },
|
|
88
88
|
React.createElement(Box, { marginBottom: 1 },
|
|
89
89
|
React.createElement(Text, null,
|
|
90
90
|
"Enter branch name for worktree at ",
|
|
@@ -92,12 +92,12 @@ const NewWorktree = ({ onComplete, onCancel }) => {
|
|
|
92
92
|
":")),
|
|
93
93
|
React.createElement(Box, null,
|
|
94
94
|
React.createElement(Text, { color: "cyan" }, '> '),
|
|
95
|
-
React.createElement(
|
|
95
|
+
React.createElement(TextInputWrapper, { value: branch, onChange: setBranch, onSubmit: handleBranchSubmit, placeholder: "e.g., feature/new-feature" })))) : step === 'branch' ? (React.createElement(Box, { flexDirection: "column" },
|
|
96
96
|
React.createElement(Box, { marginBottom: 1 },
|
|
97
97
|
React.createElement(Text, null, "Enter branch name (directory will be auto-generated):")),
|
|
98
98
|
React.createElement(Box, null,
|
|
99
99
|
React.createElement(Text, { color: "cyan" }, '> '),
|
|
100
|
-
React.createElement(
|
|
100
|
+
React.createElement(TextInputWrapper, { value: branch, onChange: setBranch, onSubmit: handleBranchSubmit, placeholder: "e.g., feature/new-feature" })),
|
|
101
101
|
generatedPath && (React.createElement(Box, { marginTop: 1 },
|
|
102
102
|
React.createElement(Text, { dimColor: true },
|
|
103
103
|
"Worktree will be created at:",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface TextInputWrapperProps {
|
|
3
|
+
value: string;
|
|
4
|
+
onChange: (value: string) => void;
|
|
5
|
+
onSubmit?: (value: string) => void;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
focus?: boolean;
|
|
8
|
+
mask?: string;
|
|
9
|
+
showCursor?: boolean;
|
|
10
|
+
highlightPastedText?: boolean;
|
|
11
|
+
}
|
|
12
|
+
declare const TextInputWrapper: React.FC<TextInputWrapperProps>;
|
|
13
|
+
export default TextInputWrapper;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import TextInput from 'ink-text-input';
|
|
3
|
+
import stripAnsi from 'strip-ansi';
|
|
4
|
+
const TextInputWrapper = ({ value, onChange, ...props }) => {
|
|
5
|
+
const handleChange = (newValue) => {
|
|
6
|
+
// First strip all ANSI escape sequences
|
|
7
|
+
let cleanedValue = stripAnsi(newValue);
|
|
8
|
+
// Then specifically remove bracketed paste mode markers that might remain
|
|
9
|
+
// These sometimes appear as literal text after ANSI stripping
|
|
10
|
+
cleanedValue = cleanedValue.replace(/\[200~/g, '').replace(/\[201~/g, '');
|
|
11
|
+
onChange(cleanedValue);
|
|
12
|
+
};
|
|
13
|
+
return React.createElement(TextInput, { value: value, onChange: handleChange, ...props });
|
|
14
|
+
};
|
|
15
|
+
export default TextInputWrapper;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
+
import stripAnsi from 'strip-ansi';
|
|
2
3
|
import { getStatusDisplay } from '../constants/statusIcons.js';
|
|
3
4
|
import { formatGitFileChanges, formatGitAheadBehind, formatParentBranch, } from './gitStatus.js';
|
|
4
5
|
// Constants
|
|
5
6
|
const MAX_BRANCH_NAME_LENGTH = 40; // Maximum characters for branch name display
|
|
6
7
|
const MIN_COLUMN_PADDING = 2; // Minimum spaces between columns
|
|
7
|
-
// Strip ANSI escape codes for length calculation
|
|
8
|
-
const stripAnsi = (str) => str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
9
8
|
// Utility function to truncate strings with ellipsis
|
|
10
9
|
export function truncateString(str, maxLength) {
|
|
11
10
|
if (str.length <= maxLength)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccmanager",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"description": "TUI application for managing multiple Claude Code sessions across Git worktrees",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Kodai Kabasawa",
|
|
@@ -45,7 +45,8 @@
|
|
|
45
45
|
"ink-text-input": "^5.0.1",
|
|
46
46
|
"meow": "^11.0.0",
|
|
47
47
|
"node-pty": "^1.0.0",
|
|
48
|
-
"react": "^18.2.0"
|
|
48
|
+
"react": "^18.2.0",
|
|
49
|
+
"strip-ansi": "^7.1.0"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@eslint/js": "^9.28.0",
|