termcast 1.3.54 → 1.4.0
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/action-utils.d.ts.map +1 -1
- package/dist/action-utils.js +17 -132
- package/dist/action-utils.js.map +1 -1
- package/dist/apis/cache.d.ts +8 -30
- package/dist/apis/cache.d.ts.map +1 -1
- package/dist/apis/cache.js +9 -271
- package/dist/apis/cache.js.map +1 -1
- package/dist/apis/clipboard.d.ts +4 -2
- package/dist/apis/clipboard.d.ts.map +1 -1
- package/dist/apis/clipboard.js +18 -31
- package/dist/apis/clipboard.js.map +1 -1
- package/dist/apis/environment.d.ts.map +1 -1
- package/dist/apis/environment.js +14 -49
- package/dist/apis/environment.js.map +1 -1
- package/dist/apis/localstorage.d.ts +7 -12
- package/dist/apis/localstorage.d.ts.map +1 -1
- package/dist/apis/localstorage.js +7 -184
- package/dist/apis/localstorage.js.map +1 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +16 -15
- package/dist/app.js.map +1 -1
- package/dist/cli.js +7 -6
- package/dist/cli.js.map +1 -1
- package/dist/components/actions.d.ts.map +1 -1
- package/dist/components/actions.js +13 -2
- package/dist/components/actions.js.map +1 -1
- package/dist/components/extension-preferences.d.ts.map +1 -1
- package/dist/components/extension-preferences.js +7 -8
- package/dist/components/extension-preferences.js.map +1 -1
- package/dist/components/form/file-autocomplete.js +2 -2
- package/dist/components/form/file-autocomplete.js.map +1 -1
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +242 -14
- package/dist/components/list.js.map +1 -1
- package/dist/e2e-node.d.ts.map +1 -1
- package/dist/e2e-node.js +5 -4
- package/dist/e2e-node.js.map +1 -1
- package/dist/extensions/dev.d.ts.map +1 -1
- package/dist/extensions/dev.js +5 -2
- package/dist/extensions/dev.js.map +1 -1
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +2 -1
- package/dist/globals.js.map +1 -1
- package/dist/internal/error-handler.d.ts.map +1 -1
- package/dist/internal/error-handler.js +21 -19
- package/dist/internal/error-handler.js.map +1 -1
- package/dist/internal/providers.d.ts.map +1 -1
- package/dist/internal/providers.js +41 -1
- package/dist/internal/providers.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +31 -29
- package/dist/logger.js.map +1 -1
- package/dist/platform/browser/cache.d.ts +41 -0
- package/dist/platform/browser/cache.d.ts.map +1 -0
- package/dist/platform/browser/cache.js +262 -0
- package/dist/platform/browser/cache.js.map +1 -0
- package/dist/platform/browser/localstorage.d.ts +20 -0
- package/dist/platform/browser/localstorage.d.ts.map +1 -0
- package/dist/platform/browser/localstorage.js +102 -0
- package/dist/platform/browser/localstorage.js.map +1 -0
- package/dist/platform/browser/runtime.d.ts +51 -0
- package/dist/platform/browser/runtime.d.ts.map +1 -0
- package/dist/platform/browser/runtime.js +164 -0
- package/dist/platform/browser/runtime.js.map +1 -0
- package/dist/platform/bun/sqlite.d.ts +17 -0
- package/dist/platform/bun/sqlite.d.ts.map +1 -0
- package/dist/platform/bun/sqlite.js +6 -0
- package/dist/platform/bun/sqlite.js.map +1 -0
- package/dist/platform/node/cache.d.ts +35 -0
- package/dist/platform/node/cache.d.ts.map +1 -0
- package/dist/platform/node/cache.js +269 -0
- package/dist/platform/node/cache.js.map +1 -0
- package/dist/platform/node/localstorage.d.ts +17 -0
- package/dist/platform/node/localstorage.d.ts.map +1 -0
- package/dist/platform/node/localstorage.js +186 -0
- package/dist/platform/node/localstorage.js.map +1 -0
- package/dist/platform/node/runtime.d.ts +52 -0
- package/dist/platform/node/runtime.d.ts.map +1 -0
- package/dist/platform/node/runtime.js +230 -0
- package/dist/platform/node/runtime.js.map +1 -0
- package/dist/platform/node/sqlite.d.ts +27 -0
- package/dist/platform/node/sqlite.d.ts.map +1 -0
- package/dist/platform/node/sqlite.js +21 -0
- package/dist/platform/node/sqlite.js.map +1 -0
- package/dist/state.d.ts +5 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +6 -28
- package/dist/state.js.map +1 -1
- package/dist/utils/file-system.d.ts.map +1 -1
- package/dist/utils/file-system.js +17 -22
- package/dist/utils/file-system.js.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +42 -47
- package/dist/utils.js.map +1 -1
- package/dist/vim-mode.d.ts +40 -0
- package/dist/vim-mode.d.ts.map +1 -0
- package/dist/vim-mode.js +135 -0
- package/dist/vim-mode.js.map +1 -0
- package/fonts/Inconsolata.otf +0 -0
- package/fonts/SIL Open Font License.txt +41 -0
- package/package.json +60 -8
- package/src/action-utils.tsx +27 -124
- package/src/apis/cache.test.ts +1 -1
- package/src/apis/cache.tsx +9 -373
- package/src/apis/clipboard.tsx +29 -38
- package/src/apis/environment.tsx +25 -52
- package/src/apis/localstorage.tsx +8 -214
- package/src/app.tsx +16 -15
- package/src/cli.tsx +14 -15
- package/src/compile.vitest.tsx +2 -2
- package/src/components/actions.tsx +19 -1
- package/src/components/extension-preferences.tsx +7 -8
- package/src/components/form/file-autocomplete.tsx +2 -2
- package/src/components/list.tsx +279 -14
- package/src/e2e-node.tsx +7 -7
- package/src/examples/action-shortcut.vitest.tsx +2 -2
- package/src/examples/actions-context.vitest.tsx +1 -1
- package/src/examples/bar-graph-weekly.vitest.tsx +10 -36
- package/src/examples/detail-metadata-showcase.vitest.tsx +36 -36
- package/src/examples/form-basic.vitest.tsx +21 -17
- package/src/examples/github.vitest.tsx +4 -4
- package/src/examples/graph-bar-chart.vitest.tsx +13 -11
- package/src/examples/graph-polymarket.vitest.tsx +2 -2
- package/src/examples/graph-row.vitest.tsx +66 -66
- package/src/examples/graph-styles.vitest.tsx +12 -12
- package/src/examples/internal/simple-scrollbox.vitest.tsx +14 -48
- package/src/examples/list-detail-metadata.vitest.tsx +5 -5
- package/src/examples/list-fetch-data.vitest.tsx +3 -3
- package/src/examples/list-item-accessories.vitest.tsx +2 -2
- package/src/examples/list-loading-empty-view.vitest.tsx +1 -1
- package/src/examples/list-no-actions.vitest.tsx +2 -2
- package/src/examples/list-scrollbox.vitest.tsx +5 -5
- package/src/examples/list-spacing-mode.vitest.tsx +3 -3
- package/src/examples/list-with-detail.vitest.tsx +68 -68
- package/src/examples/list-with-dropdown.vitest.tsx +5 -5
- package/src/examples/list-with-sections.vitest.tsx +27 -27
- package/src/examples/simple-candle-chart.vitest.tsx +7 -7
- package/src/examples/simple-detail-markdown.vitest.tsx +8 -8
- package/src/examples/simple-detail-table.vitest.tsx +8 -8
- package/src/examples/simple-graph.vitest.tsx +3 -3
- package/src/examples/simple-grid.vitest.tsx +14 -14
- package/src/examples/simple-heatmap.vitest.tsx +1 -1
- package/src/examples/simple-navigation.vitest.tsx +17 -17
- package/src/examples/simple-progress-bar.vitest.tsx +1 -1
- package/src/examples/store.vitest.tsx +1 -1
- package/src/examples/swift-extension.vitest.tsx +2 -2
- package/src/examples/table-edge-cases.vitest.tsx +18 -18
- package/src/examples/toast-action.vitest.tsx +2 -2
- package/src/extensions/dev.tsx +5 -2
- package/src/extensions/dev.vitest.tsx +3 -3
- package/src/globals.ts +2 -1
- package/src/internal/error-handler.tsx +19 -21
- package/src/internal/providers.tsx +39 -0
- package/src/logger.tsx +38 -41
- package/src/platform/browser/cache.ts +327 -0
- package/src/platform/browser/localstorage.ts +119 -0
- package/src/platform/browser/runtime.ts +209 -0
- package/src/platform/bun/sqlite.ts +19 -0
- package/src/platform/node/cache.ts +372 -0
- package/src/platform/node/localstorage.ts +214 -0
- package/src/platform/node/runtime.ts +264 -0
- package/src/platform/node/sqlite.ts +43 -0
- package/src/state.tsx +17 -28
- package/src/utils/file-system.ts +17 -22
- package/src/utils.test.tsx +1 -1
- package/src/utils.tsx +56 -47
- package/src/vim-mode.tsx +153 -0
- package/src/apis/sqlite.ts +0 -14
package/src/components/list.tsx
CHANGED
|
@@ -39,6 +39,8 @@ import { ActionPanel, matchesShortcut } from 'termcast/src/components/actions'
|
|
|
39
39
|
import { getInteractiveHoverBackground, useTheme } from 'termcast/src/theme'
|
|
40
40
|
import { Markdown } from 'termcast/src/components/markdown'
|
|
41
41
|
import { CommonProps } from 'termcast/src/utils'
|
|
42
|
+
import { getMatchingCommands, executeVimCommand } from 'termcast/src/vim-mode'
|
|
43
|
+
import { ThemePicker } from 'termcast/src/components/theme-picker'
|
|
42
44
|
|
|
43
45
|
export { Color }
|
|
44
46
|
|
|
@@ -84,6 +86,9 @@ function ListFooter(): any {
|
|
|
84
86
|
const dropdownFooterLabel = useStore((s) => s.dropdownFooterLabel)
|
|
85
87
|
const dropdownTooltip = useStore((s) => s.dropdownTooltip)
|
|
86
88
|
const hasToast = useStore((s) => s.toast !== null)
|
|
89
|
+
const inputMode = useStore((s) => s.inputMode)
|
|
90
|
+
const vimInputSubMode = useStore((s) => s.vimInputSubMode)
|
|
91
|
+
const vimCommandText = useStore((s) => s.vimCommandText)
|
|
87
92
|
const listContext = useContext(ListContext)
|
|
88
93
|
const isShowingDetail = listContext?.isShowingDetail ?? false
|
|
89
94
|
const hasDropdown = listContext?.hasDropdown ?? false
|
|
@@ -94,6 +99,23 @@ function ListFooter(): any {
|
|
|
94
99
|
}
|
|
95
100
|
}
|
|
96
101
|
|
|
102
|
+
// When in command mode, show the command input in the footer
|
|
103
|
+
if (vimInputSubMode === 'command' && !hasToast) {
|
|
104
|
+
const matchingCommands = getMatchingCommands(vimCommandText)
|
|
105
|
+
return (
|
|
106
|
+
<Footer hidePoweredBy={isShowingDetail}>
|
|
107
|
+
<box style={{ flexDirection: 'row', gap: 2, flexShrink: 0, flexGrow: 1 }}>
|
|
108
|
+
<text flexShrink={0} fg={theme.text}>:{vimCommandText}</text>
|
|
109
|
+
<text flexShrink={0} fg={theme.textMuted}>
|
|
110
|
+
{matchingCommands.map((cmd) => cmd.name).join(' · ')}
|
|
111
|
+
</text>
|
|
112
|
+
</box>
|
|
113
|
+
</Footer>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const isVim = inputMode === 'vim'
|
|
118
|
+
|
|
97
119
|
const content = hasToast ? null : (
|
|
98
120
|
<box style={{ flexDirection: 'row', gap: 3, flexShrink: 0 }}>
|
|
99
121
|
{firstActionTitle && (
|
|
@@ -110,10 +132,18 @@ function ListFooter(): any {
|
|
|
110
132
|
)}
|
|
111
133
|
<box style={{ flexDirection: 'row', gap: 1, flexShrink: 0 }}>
|
|
112
134
|
<text flexShrink={0} fg={theme.text} attributes={TextAttributes.BOLD}>
|
|
113
|
-
↑↓
|
|
135
|
+
{isVim ? 'j/k' : '↑↓'}
|
|
114
136
|
</text>
|
|
115
137
|
<text flexShrink={0} fg={theme.textMuted}>navigate</text>
|
|
116
138
|
</box>
|
|
139
|
+
{isVim && (
|
|
140
|
+
<box style={{ flexDirection: 'row', gap: 1, flexShrink: 0 }}>
|
|
141
|
+
<text flexShrink={0} fg={theme.text} attributes={TextAttributes.BOLD}>
|
|
142
|
+
/
|
|
143
|
+
</text>
|
|
144
|
+
<text flexShrink={0} fg={theme.textMuted}>search</text>
|
|
145
|
+
</box>
|
|
146
|
+
)}
|
|
117
147
|
<Hoverable
|
|
118
148
|
onMouseDown={() => {
|
|
119
149
|
useStore.setState({ showActionsDialog: true })
|
|
@@ -134,6 +164,15 @@ function ListFooter(): any {
|
|
|
134
164
|
</text>
|
|
135
165
|
</Hoverable>
|
|
136
166
|
)}
|
|
167
|
+
{!isVim && (
|
|
168
|
+
<Hoverable
|
|
169
|
+
onMouseDown={() => {
|
|
170
|
+
executeVimCommand('vim')
|
|
171
|
+
}}
|
|
172
|
+
>
|
|
173
|
+
<text flexShrink={0} fg={theme.textMuted}>:vim</text>
|
|
174
|
+
</Hoverable>
|
|
175
|
+
)}
|
|
137
176
|
</box>
|
|
138
177
|
)
|
|
139
178
|
|
|
@@ -1367,18 +1406,237 @@ export const List: ListType = (props) => {
|
|
|
1367
1406
|
|
|
1368
1407
|
const inFocus = useIsInFocus()
|
|
1369
1408
|
const dialog = useDialog()
|
|
1409
|
+
const inputMode = useStore((s) => s.inputMode)
|
|
1410
|
+
const vimInputSubMode = useStore((s) => s.vimInputSubMode)
|
|
1411
|
+
|
|
1412
|
+
// Textarea is focused when:
|
|
1413
|
+
// - raycast mode default (always focused for type-to-search)
|
|
1414
|
+
// - vim mode search (user pressed /)
|
|
1415
|
+
// - NOT in command mode (command input lives in footer, not textarea)
|
|
1416
|
+
const searchBarFocused = vimInputSubMode !== 'command' && (inputMode === 'raycast' || vimInputSubMode === 'search')
|
|
1417
|
+
|
|
1418
|
+
// Timestamp-based gg sequence detection in vim mode.
|
|
1419
|
+
// If two 'g' presses happen within 500ms, jump to first item.
|
|
1420
|
+
// Using timestamps instead of setTimeout avoids cleanup on unmount.
|
|
1421
|
+
const lastGPressedAtRef = useRef(0)
|
|
1422
|
+
|
|
1423
|
+
// Helper: jump to first visible item
|
|
1424
|
+
const moveToFirst = () => {
|
|
1425
|
+
const items = Object.values(descendantsContext.map.current)
|
|
1426
|
+
.filter((item) => item.index !== -1 && item.props?.visible !== false)
|
|
1427
|
+
.sort((a, b) => a.index - b.index)
|
|
1428
|
+
if (items[0]) {
|
|
1429
|
+
flushSync(() => {
|
|
1430
|
+
setSelectedIndex(items[0].index)
|
|
1431
|
+
})
|
|
1432
|
+
persistSelectedIndexInCurrentNavigationItem(items[0].index)
|
|
1433
|
+
scrollToItemIfNeeded({ item: items[0], direction: -1 })
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// Helper: jump to last visible item
|
|
1438
|
+
const moveToLast = () => {
|
|
1439
|
+
const items = Object.values(descendantsContext.map.current)
|
|
1440
|
+
.filter((item) => item.index !== -1 && item.props?.visible !== false)
|
|
1441
|
+
.sort((a, b) => a.index - b.index)
|
|
1442
|
+
const lastItem = items[items.length - 1]
|
|
1443
|
+
if (lastItem) {
|
|
1444
|
+
flushSync(() => {
|
|
1445
|
+
setSelectedIndex(lastItem.index)
|
|
1446
|
+
})
|
|
1447
|
+
persistSelectedIndexInCurrentNavigationItem(lastItem.index)
|
|
1448
|
+
scrollToItemIfNeeded({ item: lastItem, direction: 1 })
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
// Helper: move by N items (for half-page jumps)
|
|
1453
|
+
const moveByN = (n: number) => {
|
|
1454
|
+
const items = Object.values(descendantsContext.map.current)
|
|
1455
|
+
.filter((item) => item.index !== -1 && item.props?.visible !== false)
|
|
1456
|
+
.sort((a, b) => a.index - b.index)
|
|
1457
|
+
if (items.length === 0) return
|
|
1458
|
+
|
|
1459
|
+
const currentVisibleIndex = items.findIndex((item) => item.index === selectedIndex)
|
|
1460
|
+
if (currentVisibleIndex === -1) return
|
|
1461
|
+
|
|
1462
|
+
const nextVisibleIndex = Math.max(0, Math.min(items.length - 1, currentVisibleIndex + n))
|
|
1463
|
+
const nextItem = items[nextVisibleIndex]
|
|
1464
|
+
if (nextItem) {
|
|
1465
|
+
flushSync(() => {
|
|
1466
|
+
setSelectedIndex(nextItem.index)
|
|
1467
|
+
})
|
|
1468
|
+
persistSelectedIndexInCurrentNavigationItem(nextItem.index)
|
|
1469
|
+
scrollToItemIfNeeded({ item: nextItem, direction: n > 0 ? 1 : -1 })
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1370
1472
|
|
|
1371
1473
|
useKeyboard((evt) => {
|
|
1372
1474
|
if (!inFocus) return
|
|
1373
1475
|
|
|
1374
|
-
|
|
1476
|
+
const { inputMode, vimInputSubMode } = useStore.getState()
|
|
1477
|
+
|
|
1478
|
+
// ── Command mode: trap all keys ──
|
|
1479
|
+
// When ':' command input is active, all keystrokes are handled here.
|
|
1480
|
+
// Enter executes, Esc cancels, backspace deletes, printable chars append.
|
|
1481
|
+
// Auto-exit command mode if a toast appeared (toast needs its own key handlers).
|
|
1482
|
+
if (vimInputSubMode === 'command') {
|
|
1483
|
+
if (useStore.getState().toast) {
|
|
1484
|
+
useStore.setState({ vimInputSubMode: 'default', vimCommandText: '' })
|
|
1485
|
+
return
|
|
1486
|
+
}
|
|
1487
|
+
evt.stopPropagation()
|
|
1488
|
+
|
|
1489
|
+
if (evt.name === 'return') {
|
|
1490
|
+
const text = useStore.getState().vimCommandText
|
|
1491
|
+
const result = executeVimCommand(text)
|
|
1492
|
+
if (result === 'theme') {
|
|
1493
|
+
dialog.push({ element: <ThemePicker /> })
|
|
1494
|
+
} else if (result === 'actions') {
|
|
1495
|
+
useStore.setState({ showActionsDialog: true })
|
|
1496
|
+
} else if (result === 'filter') {
|
|
1497
|
+
if (searchBarAccessory && !isDropdownOpen) {
|
|
1498
|
+
openDropdown()
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
useStore.setState({ vimInputSubMode: 'default', vimCommandText: '' })
|
|
1502
|
+
return
|
|
1503
|
+
}
|
|
1504
|
+
if (evt.name === 'escape') {
|
|
1505
|
+
useStore.setState({ vimInputSubMode: 'default', vimCommandText: '' })
|
|
1506
|
+
return
|
|
1507
|
+
}
|
|
1508
|
+
if (evt.name === 'backspace') {
|
|
1509
|
+
const current = useStore.getState().vimCommandText
|
|
1510
|
+
if (current.length === 0) {
|
|
1511
|
+
// Backspace on empty exits command mode
|
|
1512
|
+
useStore.setState({ vimInputSubMode: 'default' })
|
|
1513
|
+
} else {
|
|
1514
|
+
useStore.setState({ vimCommandText: current.slice(0, -1) })
|
|
1515
|
+
}
|
|
1516
|
+
return
|
|
1517
|
+
}
|
|
1518
|
+
if (evt.name === 'tab') {
|
|
1519
|
+
// Tab-complete: fill in the first matching command
|
|
1520
|
+
const current = useStore.getState().vimCommandText
|
|
1521
|
+
const matches = getMatchingCommands(current)
|
|
1522
|
+
if (matches.length > 0 && matches[0]) {
|
|
1523
|
+
useStore.setState({ vimCommandText: matches[0].name })
|
|
1524
|
+
}
|
|
1525
|
+
return
|
|
1526
|
+
}
|
|
1527
|
+
// Append printable characters (single char, no modifiers except shift)
|
|
1528
|
+
if (evt.sequence.length === 1 && !evt.ctrl && !evt.meta) {
|
|
1529
|
+
useStore.setState({ vimCommandText: useStore.getState().vimCommandText + evt.sequence })
|
|
1530
|
+
}
|
|
1531
|
+
return
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// ── Vim search mode: Enter confirms, Esc clears and exits ──
|
|
1535
|
+
// Ctrl+K and Ctrl+P still work while searching (fall through to shared handlers below).
|
|
1536
|
+
if (inputMode === 'vim' && vimInputSubMode === 'search') {
|
|
1537
|
+
if (evt.name === 'return') {
|
|
1538
|
+
// Confirm search: keep search text, return to normal mode
|
|
1539
|
+
useStore.setState({ vimInputSubMode: 'default' })
|
|
1540
|
+
evt.stopPropagation()
|
|
1541
|
+
return
|
|
1542
|
+
}
|
|
1543
|
+
if (evt.name === 'escape') {
|
|
1544
|
+
// Clear search and return to normal mode
|
|
1545
|
+
useStore.setState({ vimInputSubMode: 'default' })
|
|
1546
|
+
if (inputRef.current) {
|
|
1547
|
+
inputRef.current.clear()
|
|
1548
|
+
}
|
|
1549
|
+
handleSearchChange('')
|
|
1550
|
+
evt.stopPropagation()
|
|
1551
|
+
return
|
|
1552
|
+
}
|
|
1553
|
+
// Let Ctrl+K, Ctrl+P, and registered shortcuts fall through to shared handlers.
|
|
1554
|
+
// All other keys (text input) are handled by the focused textarea.
|
|
1555
|
+
const isSharedShortcut = (evt.ctrl || evt.super || evt.meta)
|
|
1556
|
+
if (!isSharedShortcut) return
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// ── Shared: Ctrl+P for dropdown ──
|
|
1375
1560
|
if (evt.ctrl && evt.name === 'p' && searchBarAccessory && !isDropdownOpen) {
|
|
1376
1561
|
openDropdown()
|
|
1377
1562
|
return
|
|
1378
1563
|
}
|
|
1379
1564
|
|
|
1380
|
-
//
|
|
1381
|
-
|
|
1565
|
+
// ── Shared: Ctrl+K / Cmd+K for actions dialog ──
|
|
1566
|
+
if (evt.name === 'k' && (evt.ctrl || evt.super)) {
|
|
1567
|
+
useStore.setState({ showActionsDialog: true })
|
|
1568
|
+
return
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// ── Vim mode keybindings (only in default sub-mode) ──
|
|
1572
|
+
// Checked before registered action shortcuts so extensions can't hijack
|
|
1573
|
+
// core vim motions like j/k with unmodified single-letter shortcuts.
|
|
1574
|
+
if (inputMode === 'vim' && vimInputSubMode === 'default') {
|
|
1575
|
+
// j/k for navigation
|
|
1576
|
+
if (evt.name === 'j' && !evt.ctrl && !evt.meta) {
|
|
1577
|
+
move(1)
|
|
1578
|
+
evt.stopPropagation()
|
|
1579
|
+
return
|
|
1580
|
+
}
|
|
1581
|
+
if (evt.name === 'k' && !evt.ctrl && !evt.meta) {
|
|
1582
|
+
move(-1)
|
|
1583
|
+
evt.stopPropagation()
|
|
1584
|
+
return
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// G (shift+g) for last item
|
|
1588
|
+
if (evt.name === 'g' && evt.shift) {
|
|
1589
|
+
moveToLast()
|
|
1590
|
+
evt.stopPropagation()
|
|
1591
|
+
return
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
// g then g for first item (gg sequence).
|
|
1595
|
+
// Two 'g' presses within 500ms triggers jump to first item.
|
|
1596
|
+
if (evt.name === 'g' && !evt.shift && !evt.ctrl && !evt.meta) {
|
|
1597
|
+
const now = Date.now()
|
|
1598
|
+
if (now - lastGPressedAtRef.current < 500) {
|
|
1599
|
+
// Second g within window: jump to first
|
|
1600
|
+
lastGPressedAtRef.current = 0
|
|
1601
|
+
moveToFirst()
|
|
1602
|
+
} else {
|
|
1603
|
+
// First g: record timestamp, wait for second
|
|
1604
|
+
lastGPressedAtRef.current = now
|
|
1605
|
+
}
|
|
1606
|
+
evt.stopPropagation()
|
|
1607
|
+
return
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// Ctrl+d for half-page down, Ctrl+u for half-page up
|
|
1611
|
+
if (evt.ctrl && evt.name === 'd') {
|
|
1612
|
+
const viewportHeight = scrollBoxRef.current?.viewport?.height || 20
|
|
1613
|
+
moveByN(Math.floor(viewportHeight / 2))
|
|
1614
|
+
evt.stopPropagation()
|
|
1615
|
+
return
|
|
1616
|
+
}
|
|
1617
|
+
if (evt.ctrl && evt.name === 'u') {
|
|
1618
|
+
const viewportHeight = scrollBoxRef.current?.viewport?.height || 20
|
|
1619
|
+
moveByN(-Math.floor(viewportHeight / 2))
|
|
1620
|
+
evt.stopPropagation()
|
|
1621
|
+
return
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// / to enter search mode
|
|
1625
|
+
if (evt.sequence === '/' && !evt.ctrl && !evt.meta) {
|
|
1626
|
+
useStore.setState({ vimInputSubMode: 'search' })
|
|
1627
|
+
evt.stopPropagation()
|
|
1628
|
+
return
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
// : to enter command mode
|
|
1632
|
+
if (evt.sequence === ':' && !evt.ctrl && !evt.meta) {
|
|
1633
|
+
useStore.setState({ vimInputSubMode: 'command', vimCommandText: '' })
|
|
1634
|
+
evt.stopPropagation()
|
|
1635
|
+
return
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
// ── Shared: registered action shortcuts ──
|
|
1382
1640
|
const registeredShortcuts = useStore.getState().registeredActionShortcuts
|
|
1383
1641
|
for (const { shortcut, execute } of registeredShortcuts) {
|
|
1384
1642
|
if (matchesShortcut(evt, shortcut)) {
|
|
@@ -1393,28 +1651,35 @@ export const List: ListType = (props) => {
|
|
|
1393
1651
|
.sort((a, b) => a.index - b.index)
|
|
1394
1652
|
const currentItem = items.find((item) => item.index === selectedIndex)
|
|
1395
1653
|
|
|
1396
|
-
//
|
|
1397
|
-
// Always open — built-in actions (Change Theme, etc.) are always available
|
|
1398
|
-
if (evt.name === 'k' && (evt.ctrl || evt.super)) {
|
|
1399
|
-
useStore.setState({ showActionsDialog: true })
|
|
1400
|
-
return
|
|
1401
|
-
}
|
|
1402
|
-
|
|
1654
|
+
// ── Shared: arrow keys ──
|
|
1403
1655
|
if (evt.name === 'up') move(-1)
|
|
1404
1656
|
if (evt.name === 'down') move(1)
|
|
1405
|
-
|
|
1657
|
+
|
|
1658
|
+
// ── Shared: Enter to auto-execute first action ──
|
|
1406
1659
|
if (evt.name === 'return') {
|
|
1407
1660
|
if (!currentItem?.props) return
|
|
1408
|
-
|
|
1409
1661
|
if (currentItem.props.actions) {
|
|
1410
1662
|
useStore.setState({ shouldAutoExecuteFirstAction: true })
|
|
1411
1663
|
}
|
|
1664
|
+
return
|
|
1412
1665
|
}
|
|
1413
1666
|
})
|
|
1414
1667
|
|
|
1415
1668
|
const handleSearchChange = (newValue: string) => {
|
|
1416
1669
|
if (!inFocus) return
|
|
1417
1670
|
|
|
1671
|
+
// Intercept ':' in raycast mode: when the search bar was empty and user typed ':',
|
|
1672
|
+
// enter command mode instead of searching. Clear the textarea back to empty.
|
|
1673
|
+
// Only triggers from truly empty state (not from editing "foo" to ":").
|
|
1674
|
+
const wasEmpty = searchText.length === 0
|
|
1675
|
+
if (inputMode === 'raycast' && wasEmpty && newValue === ':') {
|
|
1676
|
+
if (inputRef.current) {
|
|
1677
|
+
inputRef.current.clear()
|
|
1678
|
+
}
|
|
1679
|
+
useStore.setState({ vimInputSubMode: 'command', vimCommandText: '' })
|
|
1680
|
+
return
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1418
1683
|
// Always update internal state immediately so the textarea and filtering
|
|
1419
1684
|
// stay responsive even when throttle delays the parent callback
|
|
1420
1685
|
if (controlledSearchText === undefined) {
|
|
@@ -1506,7 +1771,7 @@ export const List: ListType = (props) => {
|
|
|
1506
1771
|
{ name: 'linefeed', action: 'submit' },
|
|
1507
1772
|
]}
|
|
1508
1773
|
placeholder={searchBarPlaceholder}
|
|
1509
|
-
focused={inFocus && !isDropdownOpen}
|
|
1774
|
+
focused={inFocus && !isDropdownOpen && searchBarFocused}
|
|
1510
1775
|
initialValue={searchText}
|
|
1511
1776
|
onContentChange={() => {
|
|
1512
1777
|
const value = inputRef.current?.plainText || ''
|
package/src/e2e-node.tsx
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import * as pty from 'node-pty'
|
|
1
|
+
import { spawn as zigSpawn } from 'zigpty'
|
|
4
2
|
import { Terminal } from '@xterm/headless'
|
|
5
3
|
import { SerializeAddon } from '@xterm/addon-serialize'
|
|
6
4
|
|
|
7
5
|
export class NodeTuiDriver {
|
|
8
|
-
private pty?:
|
|
6
|
+
private pty?: ReturnType<typeof zigSpawn>
|
|
9
7
|
private term: Terminal
|
|
10
8
|
private serialize: SerializeAddon
|
|
11
9
|
private cols: number
|
|
@@ -51,16 +49,18 @@ export class NodeTuiDriver {
|
|
|
51
49
|
TERM: 'xterm-truecolor',
|
|
52
50
|
COLORTERM: 'truecolor',
|
|
53
51
|
}
|
|
54
|
-
this.pty =
|
|
52
|
+
this.pty = zigSpawn(this.cmd, this.args, {
|
|
55
53
|
name: 'xterm-truecolor',
|
|
56
54
|
cols,
|
|
57
55
|
rows,
|
|
58
56
|
cwd,
|
|
59
|
-
env: envWithTerm as
|
|
57
|
+
env: envWithTerm as Record<string, string>,
|
|
60
58
|
})
|
|
61
59
|
|
|
62
60
|
this.pty.onData((data) => {
|
|
63
|
-
|
|
61
|
+
// zigpty onData can return string or Buffer
|
|
62
|
+
const str = typeof data === 'string' ? data : data.toString()
|
|
63
|
+
this.term.write(str)
|
|
64
64
|
clearTimeout(this.idleTimer)
|
|
65
65
|
this.idleTimer = setTimeout(() => {
|
|
66
66
|
const r = this.idleResolvers.splice(0)
|
|
@@ -57,7 +57,7 @@ test('ctrl+r shortcut should trigger Refresh action directly', async () => {
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
↵ refresh ↑↓ navigate ^k actions
|
|
60
|
+
↵ refresh ↑↓ navigate ^k actions :vim
|
|
61
61
|
|
|
62
62
|
|
|
63
63
|
|
|
@@ -97,11 +97,11 @@ test('action shortcut is displayed in action panel', async () => {
|
|
|
97
97
|
│ │
|
|
98
98
|
│ Settings │
|
|
99
99
|
│ Change Theme... │
|
|
100
|
+
│ Enable Vim Mode │
|
|
100
101
|
│ Toggle Console Logs │
|
|
101
102
|
│ │
|
|
102
103
|
│ │
|
|
103
104
|
│ │
|
|
104
|
-
│ │
|
|
105
105
|
│ ↵ select ↑↓ navigate │
|
|
106
106
|
│ │"
|
|
107
107
|
`)
|
|
@@ -74,44 +74,18 @@ test('many columns (20) clips with overflow hidden', async () => {
|
|
|
74
74
|
session.sendKey('down')
|
|
75
75
|
session.sendKey('down')
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
await session.text({
|
|
78
78
|
waitFor: (t) => t.includes('›Many Columns'),
|
|
79
79
|
timeout: 10000,
|
|
80
80
|
})
|
|
81
|
+
await session.waitIdle()
|
|
82
|
+
const text = await session.text()
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
> Search...
|
|
89
|
-
|
|
90
|
-
Weekly Traffic 3 channels across 6 d │ ███
|
|
91
|
-
Revenue by Region EMEA / APAC / Amer │ ███
|
|
92
|
-
Server Load CPU / Memory / IO │ ███ ███ ███ ███ ███
|
|
93
|
-
›Many Columns (20) Overflow test with │ ███ ███ ███ ███ ███ ███ ██
|
|
94
|
-
Many Series (8) Legend overflow test │ ███ ███ ███ ███ ███ ███ ███ ███ ██
|
|
95
|
-
Long Labels Labels wider than bar co │ ███ ███ ███ ███ ███ ███ ███ ███ ██
|
|
96
|
-
Week 1 vs Week 2 Two graphs in a Row │ ███ ███ ███ ███ ███ ███ ███ ███ ██
|
|
97
|
-
│ ███ ███ ███ ███ ███ ███ ███ ███ ██
|
|
98
|
-
│ ███ ███ ███ ███ ███ ███ ███ ███ ██
|
|
99
|
-
│ D1 D2 D3 D4 D5 D6 D7 D8 D9
|
|
100
|
-
↑↓ navigate ^k actions │ ■ A ■ B
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
"
|
|
114
|
-
`)
|
|
84
|
+
// Bar graph rendering has non-deterministic ANSI highlights, so use toContain checks
|
|
85
|
+
// instead of inline snapshot for the bars area
|
|
86
|
+
expect(text).toContain('›Many Columns')
|
|
87
|
+
expect(text).toContain('BarGraph Showcase')
|
|
88
|
+
expect(text).toContain('███')
|
|
115
89
|
|
|
116
90
|
// Some labels visible, overflow clips the rest
|
|
117
91
|
expect(text).toContain('D1')
|
|
@@ -149,7 +123,7 @@ test('many series (8) legend clips on one line', async () => {
|
|
|
149
123
|
│ ███ ███ ███ ███ ███ ███
|
|
150
124
|
│ ███ ███ ███ ███ ███ ███
|
|
151
125
|
│ Mon Tue Wed Thu Fri Sat
|
|
152
|
-
↑↓ navigate ^k actions
|
|
126
|
+
↑↓ navigate ^k actions :vim │ ■ Series 1 ■ Series 2 ■ Series 3 ■
|
|
153
127
|
|
|
154
128
|
|
|
155
129
|
|
|
@@ -202,7 +176,7 @@ test('long labels truncated by overflow hidden', async () => {
|
|
|
202
176
|
│ ███ ███ ███ ███ ███ ███
|
|
203
177
|
│ ███ ███ ███ ███ ███ ███
|
|
204
178
|
│ Mon Tue Wed Thu Fri Sat
|
|
205
|
-
↑↓ navigate ^k actions
|
|
179
|
+
↑↓ navigate ^k actions :vim │ ■ Views ■ Clicks
|
|
206
180
|
|
|
207
181
|
|
|
208
182
|
|
|
@@ -48,39 +48,31 @@ test('detail metadata showcase renders markdown and metadata together', async ()
|
|
|
48
48
|
|
|
49
49
|
Project Update: Q1 2024 Review
|
|
50
50
|
|
|
51
|
-
This detail view demonstrates markdown content alongside metadata.
|
|
52
51
|
|
|
52
|
+
This detail view demonstrates markdown content alongside metadata.
|
|
53
53
|
---
|
|
54
|
-
|
|
55
54
|
Summary
|
|
56
55
|
|
|
57
|
-
The project has made significant progress this quarter. Key highlights include:
|
|
58
56
|
|
|
57
|
+
The project has made significant progress this quarter. Key highlights include:
|
|
59
58
|
- Completed the new authentication system
|
|
60
59
|
- Migrated 85% of users to the new platform
|
|
61
60
|
- Reduced API response time by 40%
|
|
62
|
-
|
|
63
|
-
|
|
64
61
|
Technical Details
|
|
65
62
|
|
|
66
|
-
The refactoring effort focused on three main areas:
|
|
67
63
|
|
|
64
|
+
The refactoring effort focused on three main areas:
|
|
68
65
|
1. Database optimization - Indexed frequently queried columns
|
|
69
66
|
2. Caching layer - Added Redis for session management
|
|
70
67
|
3. Code cleanup - Removed deprecated endpoints
|
|
71
|
-
|
|
72
|
-
|
|
73
68
|
Next Steps
|
|
74
69
|
|
|
75
|
-
We will continue with Phase 2 in the upcoming sprint. The team should prioritize:
|
|
76
70
|
|
|
71
|
+
We will continue with Phase 2 in the upcoming sprint. The team should prioritize:
|
|
77
72
|
- Finishing the remaining user migrations
|
|
78
73
|
- Implementing the new dashboard
|
|
79
74
|
- Writing integration tests
|
|
80
|
-
|
|
81
|
-
|
|
82
75
|
---
|
|
83
|
-
|
|
84
76
|
Last updated: January 20, 2024
|
|
85
77
|
|
|
86
78
|
Basic Information
|
|
@@ -160,6 +152,14 @@ test('detail metadata showcase renders markdown and metadata together', async ()
|
|
|
160
152
|
|
|
161
153
|
|
|
162
154
|
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
163
|
|
|
164
164
|
"
|
|
165
165
|
`)
|
|
@@ -219,39 +219,31 @@ test('detail metadata renders long values in column layout', async () => {
|
|
|
219
219
|
|
|
220
220
|
Project Update: Q1 2024 Review
|
|
221
221
|
|
|
222
|
-
This detail view demonstrates markdown content alongside metadata.
|
|
223
222
|
|
|
223
|
+
This detail view demonstrates markdown content alongside metadata.
|
|
224
224
|
---
|
|
225
|
-
|
|
226
225
|
Summary
|
|
227
226
|
|
|
228
|
-
The project has made significant progress this quarter. Key highlights include:
|
|
229
227
|
|
|
228
|
+
The project has made significant progress this quarter. Key highlights include:
|
|
230
229
|
- Completed the new authentication system
|
|
231
230
|
- Migrated 85% of users to the new platform
|
|
232
231
|
- Reduced API response time by 40%
|
|
233
|
-
|
|
234
|
-
|
|
235
232
|
Technical Details
|
|
236
233
|
|
|
237
|
-
The refactoring effort focused on three main areas:
|
|
238
234
|
|
|
235
|
+
The refactoring effort focused on three main areas:
|
|
239
236
|
1. Database optimization - Indexed frequently queried columns
|
|
240
237
|
2. Caching layer - Added Redis for session management
|
|
241
238
|
3. Code cleanup - Removed deprecated endpoints
|
|
242
|
-
|
|
243
|
-
|
|
244
239
|
Next Steps
|
|
245
240
|
|
|
246
|
-
We will continue with Phase 2 in the upcoming sprint. The team should prioritize:
|
|
247
241
|
|
|
242
|
+
We will continue with Phase 2 in the upcoming sprint. The team should prioritize:
|
|
248
243
|
- Finishing the remaining user migrations
|
|
249
244
|
- Implementing the new dashboard
|
|
250
245
|
- Writing integration tests
|
|
251
|
-
|
|
252
|
-
|
|
253
246
|
---
|
|
254
|
-
|
|
255
247
|
Last updated: January 20, 2024
|
|
256
248
|
|
|
257
249
|
Basic Information
|
|
@@ -331,6 +323,14 @@ test('detail metadata renders long values in column layout', async () => {
|
|
|
331
323
|
|
|
332
324
|
|
|
333
325
|
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
|
|
334
334
|
|
|
335
335
|
"
|
|
336
336
|
`)
|
|
@@ -400,39 +400,31 @@ test('detail metadata renders tag lists with multiple items', async () => {
|
|
|
400
400
|
|
|
401
401
|
Project Update: Q1 2024 Review
|
|
402
402
|
|
|
403
|
-
This detail view demonstrates markdown content alongside metadata.
|
|
404
403
|
|
|
404
|
+
This detail view demonstrates markdown content alongside metadata.
|
|
405
405
|
---
|
|
406
|
-
|
|
407
406
|
Summary
|
|
408
407
|
|
|
409
|
-
The project has made significant progress this quarter. Key highlights include:
|
|
410
408
|
|
|
409
|
+
The project has made significant progress this quarter. Key highlights include:
|
|
411
410
|
- Completed the new authentication system
|
|
412
411
|
- Migrated 85% of users to the new platform
|
|
413
412
|
- Reduced API response time by 40%
|
|
414
|
-
|
|
415
|
-
|
|
416
413
|
Technical Details
|
|
417
414
|
|
|
418
|
-
The refactoring effort focused on three main areas:
|
|
419
415
|
|
|
416
|
+
The refactoring effort focused on three main areas:
|
|
420
417
|
1. Database optimization - Indexed frequently queried columns
|
|
421
418
|
2. Caching layer - Added Redis for session management
|
|
422
419
|
3. Code cleanup - Removed deprecated endpoints
|
|
423
|
-
|
|
424
|
-
|
|
425
420
|
Next Steps
|
|
426
421
|
|
|
427
|
-
We will continue with Phase 2 in the upcoming sprint. The team should prioritize:
|
|
428
422
|
|
|
423
|
+
We will continue with Phase 2 in the upcoming sprint. The team should prioritize:
|
|
429
424
|
- Finishing the remaining user migrations
|
|
430
425
|
- Implementing the new dashboard
|
|
431
426
|
- Writing integration tests
|
|
432
|
-
|
|
433
|
-
|
|
434
427
|
---
|
|
435
|
-
|
|
436
428
|
Last updated: January 20, 2024
|
|
437
429
|
|
|
438
430
|
Basic Information
|
|
@@ -512,6 +504,14 @@ test('detail metadata renders tag lists with multiple items', async () => {
|
|
|
512
504
|
|
|
513
505
|
|
|
514
506
|
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
|
|
515
515
|
|
|
516
516
|
"
|
|
517
517
|
`)
|