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/utils.tsx
CHANGED
|
@@ -2,9 +2,22 @@ import React, { type ReactNode } from 'react'
|
|
|
2
2
|
import { createRoot } from '@opentui/react'
|
|
3
3
|
import { createCliRenderer } from '@opentui/core'
|
|
4
4
|
import { TermcastProvider } from 'termcast/src/internal/providers'
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import {
|
|
6
|
+
joinPath,
|
|
7
|
+
basename,
|
|
8
|
+
resolvePath,
|
|
9
|
+
homedir,
|
|
10
|
+
platform,
|
|
11
|
+
exit,
|
|
12
|
+
fileExists,
|
|
13
|
+
readFileSync,
|
|
14
|
+
ensureDir,
|
|
15
|
+
execCommand,
|
|
16
|
+
readdirSync,
|
|
17
|
+
rmSync,
|
|
18
|
+
mkdirSync,
|
|
19
|
+
cpSync,
|
|
20
|
+
} from '#platform/runtime'
|
|
8
21
|
import {
|
|
9
22
|
parsePackageJson,
|
|
10
23
|
type RaycastPackageJson,
|
|
@@ -14,6 +27,7 @@ import {
|
|
|
14
27
|
import { logger } from './logger'
|
|
15
28
|
import { useStore } from './state'
|
|
16
29
|
import { initializeTheme } from './theme'
|
|
30
|
+
import { initializeVimMode } from './vim-mode'
|
|
17
31
|
import { isAppMode } from './apis/environment'
|
|
18
32
|
|
|
19
33
|
export interface RenderWithProvidersOptions {
|
|
@@ -39,7 +53,7 @@ export async function renderWithProviders(
|
|
|
39
53
|
const extensionName = options?.extensionName || 'termcast-app'
|
|
40
54
|
const extensionPath =
|
|
41
55
|
options?.extensionPath ||
|
|
42
|
-
|
|
56
|
+
joinPath(homedir(), '.termcast', 'compiled', extensionName)
|
|
43
57
|
const packageJson: RaycastPackageJson = options?.packageJson || {
|
|
44
58
|
name: extensionName,
|
|
45
59
|
title: extensionName,
|
|
@@ -55,15 +69,16 @@ export async function renderWithProviders(
|
|
|
55
69
|
extensionPackageJson: packageJson,
|
|
56
70
|
})
|
|
57
71
|
|
|
58
|
-
// Load theme after state reset — extensionPath is now set so it reads from the correct DB
|
|
72
|
+
// Load theme and vim mode after state reset — extensionPath is now set so it reads from the correct DB
|
|
59
73
|
initializeTheme()
|
|
74
|
+
initializeVimMode()
|
|
60
75
|
|
|
61
76
|
const renderer = await createCliRenderer({
|
|
62
77
|
onDestroy: () => {
|
|
63
78
|
// In app mode, destroying the renderer should not kill the process.
|
|
64
79
|
// The app launcher manages the process lifecycle.
|
|
65
80
|
if (!isAppMode()) {
|
|
66
|
-
|
|
81
|
+
exit(0)
|
|
67
82
|
}
|
|
68
83
|
},
|
|
69
84
|
})
|
|
@@ -71,7 +86,7 @@ export async function renderWithProviders(
|
|
|
71
86
|
createRoot(renderer).render(<TermcastProvider>{element}</TermcastProvider>)
|
|
72
87
|
}
|
|
73
88
|
|
|
74
|
-
export const termcastMaxContentWidth =
|
|
89
|
+
export const termcastMaxContentWidth = 240
|
|
75
90
|
|
|
76
91
|
export type CommonProps = {
|
|
77
92
|
key?: any
|
|
@@ -92,7 +107,7 @@ export function sleep(ms: number): Promise<void> {
|
|
|
92
107
|
}
|
|
93
108
|
|
|
94
109
|
export async function getApplications(path?: PathLike): Promise<Application[]> {
|
|
95
|
-
if (
|
|
110
|
+
if (platform !== 'darwin') {
|
|
96
111
|
// Windows/Linux: return empty for now
|
|
97
112
|
return []
|
|
98
113
|
}
|
|
@@ -117,7 +132,7 @@ export async function getApplications(path?: PathLike): Promise<Application[]> {
|
|
|
117
132
|
try {
|
|
118
133
|
const infoPlistPath = `${appPath}/Contents/Info.plist`
|
|
119
134
|
|
|
120
|
-
if (!
|
|
135
|
+
if (!fileExists(infoPlistPath)) {
|
|
121
136
|
continue
|
|
122
137
|
}
|
|
123
138
|
|
|
@@ -164,7 +179,7 @@ export async function getDefaultApplication(
|
|
|
164
179
|
}
|
|
165
180
|
|
|
166
181
|
export async function getFrontmostApplication(): Promise<Application> {
|
|
167
|
-
if (
|
|
182
|
+
if (platform !== 'darwin') {
|
|
168
183
|
throw new Error('getFrontmostApplication is only supported on macOS')
|
|
169
184
|
}
|
|
170
185
|
|
|
@@ -196,10 +211,10 @@ export async function getFrontmostApplication(): Promise<Application> {
|
|
|
196
211
|
export async function showInFinder(path: PathLike): Promise<void> {
|
|
197
212
|
const pathStr = typeof path === 'string' ? path : path.toString()
|
|
198
213
|
|
|
199
|
-
if (
|
|
214
|
+
if (platform !== 'darwin') {
|
|
200
215
|
// On non-macOS, just open the parent directory
|
|
201
|
-
const { dirname } = await import('node:path')
|
|
202
|
-
return open(
|
|
216
|
+
const { dirname: pathDirname } = await import('node:path')
|
|
217
|
+
return open(pathDirname(pathStr))
|
|
203
218
|
}
|
|
204
219
|
|
|
205
220
|
const { spawn } = await import('node:child_process')
|
|
@@ -222,7 +237,7 @@ export async function trash(path: PathLike | PathLike[]): Promise<void> {
|
|
|
222
237
|
const paths = Array.isArray(path) ? path : [path]
|
|
223
238
|
const pathStrs = paths.map((p) => (typeof p === 'string' ? p : p.toString()))
|
|
224
239
|
|
|
225
|
-
if (
|
|
240
|
+
if (platform === 'darwin') {
|
|
226
241
|
const { execSync } = await import('node:child_process')
|
|
227
242
|
|
|
228
243
|
for (const filePath of pathStrs) {
|
|
@@ -232,7 +247,7 @@ export async function trash(path: PathLike | PathLike[]): Promise<void> {
|
|
|
232
247
|
{ stdio: 'ignore' },
|
|
233
248
|
)
|
|
234
249
|
}
|
|
235
|
-
} else if (
|
|
250
|
+
} else if (platform === 'win32') {
|
|
236
251
|
// Windows: use PowerShell to move to recycle bin
|
|
237
252
|
const { execSync } = await import('node:child_process')
|
|
238
253
|
|
|
@@ -276,11 +291,11 @@ export async function open(
|
|
|
276
291
|
let cmd: string
|
|
277
292
|
let args: string[]
|
|
278
293
|
|
|
279
|
-
if (
|
|
294
|
+
if (platform === 'darwin') {
|
|
280
295
|
// macOS
|
|
281
296
|
cmd = 'open'
|
|
282
297
|
args = appName ? ['-a', appName, target] : [target]
|
|
283
|
-
} else if (
|
|
298
|
+
} else if (platform === 'win32') {
|
|
284
299
|
// Windows
|
|
285
300
|
cmd = 'cmd'
|
|
286
301
|
args = ['/c', 'start', '', target]
|
|
@@ -304,11 +319,11 @@ export function captureException(exception: unknown): void {
|
|
|
304
319
|
|
|
305
320
|
export function getExtensionPath(extensionName: string): string {
|
|
306
321
|
const storeDir = getStoreDirectory()
|
|
307
|
-
return
|
|
322
|
+
return joinPath(storeDir, extensionName)
|
|
308
323
|
}
|
|
309
324
|
|
|
310
325
|
export function getExtensionPackageJsonPath(extensionName: string): string {
|
|
311
|
-
return
|
|
326
|
+
return joinPath(getExtensionPath(extensionName), 'package.json')
|
|
312
327
|
}
|
|
313
328
|
|
|
314
329
|
export function getExtensionPackageJson(
|
|
@@ -316,7 +331,7 @@ export function getExtensionPackageJson(
|
|
|
316
331
|
): RaycastPackageJson | null {
|
|
317
332
|
const packageJsonPath = getExtensionPackageJsonPath(extensionName)
|
|
318
333
|
|
|
319
|
-
if (!
|
|
334
|
+
if (!fileExists(packageJsonPath)) {
|
|
320
335
|
return null
|
|
321
336
|
}
|
|
322
337
|
|
|
@@ -394,15 +409,15 @@ export function resolveCommandPath({
|
|
|
394
409
|
dir: string
|
|
395
410
|
}): string {
|
|
396
411
|
// First check for .termcast-bundle directory
|
|
397
|
-
const bundleDir =
|
|
398
|
-
const bundledPath =
|
|
399
|
-
if (
|
|
412
|
+
const bundleDir = joinPath(dir, '.termcast-bundle')
|
|
413
|
+
const bundledPath = joinPath(bundleDir, `${commandName}.js`)
|
|
414
|
+
if (fileExists(bundledPath)) {
|
|
400
415
|
return bundledPath
|
|
401
416
|
}
|
|
402
417
|
|
|
403
418
|
// Then check for top-level command file
|
|
404
|
-
const topLevelPath =
|
|
405
|
-
if (
|
|
419
|
+
const topLevelPath = joinPath(dir, `${commandName}.js`)
|
|
420
|
+
if (fileExists(topLevelPath)) {
|
|
406
421
|
return topLevelPath
|
|
407
422
|
}
|
|
408
423
|
|
|
@@ -411,13 +426,11 @@ export function resolveCommandPath({
|
|
|
411
426
|
}
|
|
412
427
|
|
|
413
428
|
export function getStoreDirectory(): string {
|
|
414
|
-
const homeDir =
|
|
415
|
-
const storeDir =
|
|
429
|
+
const homeDir = homedir()
|
|
430
|
+
const storeDir = joinPath(homeDir, '.termcast', 'store')
|
|
416
431
|
|
|
417
432
|
// Ensure store directory exists
|
|
418
|
-
|
|
419
|
-
fs.mkdirSync(storeDir, { recursive: true })
|
|
420
|
-
}
|
|
433
|
+
ensureDir(storeDir)
|
|
421
434
|
|
|
422
435
|
return storeDir
|
|
423
436
|
}
|
|
@@ -430,18 +443,14 @@ export function installExtension({
|
|
|
430
443
|
extensionSourcePath: string
|
|
431
444
|
}): void {
|
|
432
445
|
const storeDir = getStoreDirectory()
|
|
433
|
-
const extensionDir =
|
|
446
|
+
const extensionDir = joinPath(storeDir, extensionName)
|
|
434
447
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
fs.rmSync(extensionDir, { recursive: true, force: true })
|
|
448
|
+
if (fileExists(extensionDir)) {
|
|
449
|
+
rmSync(extensionDir)
|
|
438
450
|
}
|
|
439
451
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
// Copy entire extension source directory
|
|
444
|
-
fs.cpSync(extensionSourcePath, extensionDir, { recursive: true })
|
|
452
|
+
mkdirSync(extensionDir)
|
|
453
|
+
cpSync(extensionSourcePath, extensionDir)
|
|
445
454
|
|
|
446
455
|
logger.log(`Extension '${extensionName}' installed to ${extensionDir}`)
|
|
447
456
|
}
|
|
@@ -450,19 +459,19 @@ export function getStoredExtensions(): StoredExtension[] {
|
|
|
450
459
|
const storeDir = getStoreDirectory()
|
|
451
460
|
const extensions: StoredExtension[] = []
|
|
452
461
|
|
|
453
|
-
if (!
|
|
462
|
+
if (!fileExists(storeDir)) {
|
|
454
463
|
return extensions
|
|
455
464
|
}
|
|
456
465
|
|
|
457
|
-
const entries =
|
|
466
|
+
const entries = readdirSync(storeDir)
|
|
458
467
|
|
|
459
468
|
for (const entry of entries) {
|
|
460
469
|
if (!entry.isDirectory()) continue
|
|
461
470
|
|
|
462
|
-
const extensionDir =
|
|
463
|
-
const packageJsonPath =
|
|
471
|
+
const extensionDir = joinPath(storeDir, entry.name)
|
|
472
|
+
const packageJsonPath = joinPath(extensionDir, 'package.json')
|
|
464
473
|
|
|
465
|
-
if (!
|
|
474
|
+
if (!fileExists(packageJsonPath)) {
|
|
466
475
|
logger.log(`Skipping ${entry.name}: no package.json found`)
|
|
467
476
|
continue
|
|
468
477
|
}
|
|
@@ -560,7 +569,7 @@ export async function runAppleScript<T = string>(
|
|
|
560
569
|
arguments_?: string[],
|
|
561
570
|
options?: ExecuteOptions<T>,
|
|
562
571
|
): Promise<T> {
|
|
563
|
-
if (
|
|
572
|
+
if (platform !== 'darwin') {
|
|
564
573
|
throw new Error('runAppleScript is only supported on macOS')
|
|
565
574
|
}
|
|
566
575
|
|
|
@@ -821,9 +830,9 @@ export async function executeSQL<T = unknown>(
|
|
|
821
830
|
databasePath: string,
|
|
822
831
|
query: string,
|
|
823
832
|
): Promise<T[]> {
|
|
824
|
-
const { Database } = await import('
|
|
833
|
+
const { Database } = await import('#sqlite')
|
|
825
834
|
|
|
826
|
-
if (!
|
|
835
|
+
if (!fileExists(databasePath)) {
|
|
827
836
|
throw new Error(`Database file not found: ${databasePath}`)
|
|
828
837
|
}
|
|
829
838
|
|
package/src/vim-mode.tsx
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vim mode persistence and command execution.
|
|
3
|
+
* Mirrors theme.tsx pattern: global Cache storage (not per-extension),
|
|
4
|
+
* load/persist/initialize functions, and a command registry.
|
|
5
|
+
*
|
|
6
|
+
* Vim mode has two sub-modes (vimInputSubMode in state):
|
|
7
|
+
* - 'default': j/k navigate, textarea unfocused, keystrokes go to useKeyboard
|
|
8
|
+
* - 'search': activated by /, textarea focused, live-filtering like raycast mode
|
|
9
|
+
* - 'command': activated by : (from empty search bar), footer shows command input
|
|
10
|
+
*
|
|
11
|
+
* The ':' command system works in both raycast and vim modes. In raycast mode,
|
|
12
|
+
* ':' is intercepted when the search bar is empty via onContentChange. In vim mode,
|
|
13
|
+
* ':' is caught directly by useKeyboard since the textarea is unfocused.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Cache } from 'termcast/src/apis/cache'
|
|
17
|
+
import { useStore, type InputMode } from 'termcast/src/state'
|
|
18
|
+
import { logger } from 'termcast/src/logger'
|
|
19
|
+
|
|
20
|
+
const VIM_MODE_STORAGE_KEY = 'termcast.vimMode'
|
|
21
|
+
|
|
22
|
+
let globalCache: Cache | null = null
|
|
23
|
+
let globalCachePath: string | null = null
|
|
24
|
+
|
|
25
|
+
function getGlobalCache(): Cache {
|
|
26
|
+
const currentPath = useStore.getState().extensionPath
|
|
27
|
+
if (!globalCache || currentPath !== globalCachePath) {
|
|
28
|
+
globalCache = new Cache()
|
|
29
|
+
globalCachePath = currentPath
|
|
30
|
+
}
|
|
31
|
+
return globalCache
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function loadPersistedInputMode(): InputMode {
|
|
35
|
+
try {
|
|
36
|
+
const stored = getGlobalCache().get(VIM_MODE_STORAGE_KEY)
|
|
37
|
+
if (stored === 'vim' || stored === 'raycast') {
|
|
38
|
+
return stored
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// Ignore errors on load
|
|
42
|
+
}
|
|
43
|
+
return 'raycast'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function persistInputMode(mode: InputMode): void {
|
|
47
|
+
try {
|
|
48
|
+
getGlobalCache().set(VIM_MODE_STORAGE_KEY, mode)
|
|
49
|
+
} catch {
|
|
50
|
+
// Ignore errors on save
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function initializeVimMode(): void {
|
|
55
|
+
const mode = loadPersistedInputMode()
|
|
56
|
+
useStore.setState({ inputMode: mode })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Command registry for ':' commands.
|
|
60
|
+
// Each command has a name and an execute function.
|
|
61
|
+
// Some commands need special handling by the caller (e.g. opening dialogs,
|
|
62
|
+
// accessing component refs). These return their name from executeVimCommand()
|
|
63
|
+
// so the caller can handle them.
|
|
64
|
+
export interface VimCommand {
|
|
65
|
+
name: string
|
|
66
|
+
execute: () => void
|
|
67
|
+
// Only show this command when in a specific mode (undefined = show always)
|
|
68
|
+
showWhenMode?: InputMode
|
|
69
|
+
// Commands that need the caller to do something (open dialog, access refs)
|
|
70
|
+
// return their name from executeVimCommand() instead of handling everything here.
|
|
71
|
+
handledByCaller?: boolean
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const VIM_COMMANDS: VimCommand[] = [
|
|
75
|
+
{
|
|
76
|
+
name: 'vim',
|
|
77
|
+
showWhenMode: 'raycast',
|
|
78
|
+
execute: () => {
|
|
79
|
+
useStore.setState({ inputMode: 'vim', vimInputSubMode: 'default', vimCommandText: '' })
|
|
80
|
+
persistInputMode('vim')
|
|
81
|
+
logger.log('Vim mode enabled')
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'theme',
|
|
86
|
+
handledByCaller: true,
|
|
87
|
+
execute: () => {
|
|
88
|
+
useStore.setState({ vimInputSubMode: 'default', vimCommandText: '' })
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'actions',
|
|
93
|
+
handledByCaller: true,
|
|
94
|
+
execute: () => {
|
|
95
|
+
useStore.setState({ vimInputSubMode: 'default', vimCommandText: '' })
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'filter',
|
|
100
|
+
handledByCaller: true,
|
|
101
|
+
execute: () => {
|
|
102
|
+
useStore.setState({ vimInputSubMode: 'default', vimCommandText: '' })
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'q',
|
|
107
|
+
execute: () => {
|
|
108
|
+
useStore.setState({ vimInputSubMode: 'default', vimCommandText: '' })
|
|
109
|
+
process.exit(0)
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get commands matching the current typed text, filtered by current mode.
|
|
116
|
+
*/
|
|
117
|
+
export function getMatchingCommands(text: string): VimCommand[] {
|
|
118
|
+
const currentMode = useStore.getState().inputMode
|
|
119
|
+
return VIM_COMMANDS.filter((cmd) => {
|
|
120
|
+
if (cmd.showWhenMode && cmd.showWhenMode !== currentMode) return false
|
|
121
|
+
if (text.length === 0) return true
|
|
122
|
+
return cmd.name.startsWith(text)
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Execute a command by name.
|
|
128
|
+
* Returns the command name if it needs caller handling (e.g. 'theme', 'actions', 'filter').
|
|
129
|
+
* Returns true if the command executed fully.
|
|
130
|
+
* Returns false if no matching command was found.
|
|
131
|
+
*/
|
|
132
|
+
export function executeVimCommand(text: string): string | boolean {
|
|
133
|
+
const currentMode = useStore.getState().inputMode
|
|
134
|
+
const command = VIM_COMMANDS.find((cmd) => {
|
|
135
|
+
if (cmd.showWhenMode && cmd.showWhenMode !== currentMode) return false
|
|
136
|
+
return cmd.name === text
|
|
137
|
+
})
|
|
138
|
+
if (!command) return false
|
|
139
|
+
command.execute()
|
|
140
|
+
if (command.handledByCaller) return command.name
|
|
141
|
+
return true
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Toggle vim mode on/off. Used by the action panel "Toggle Vim Mode" action.
|
|
146
|
+
*/
|
|
147
|
+
export function toggleVimMode(): void {
|
|
148
|
+
const currentMode = useStore.getState().inputMode
|
|
149
|
+
const newMode: InputMode = currentMode === 'vim' ? 'raycast' : 'vim'
|
|
150
|
+
useStore.setState({ inputMode: newMode, vimInputSubMode: 'default', vimCommandText: '' })
|
|
151
|
+
persistInputMode(newMode)
|
|
152
|
+
logger.log(`Input mode: ${newMode}`)
|
|
153
|
+
}
|
package/src/apis/sqlite.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SQLite abstraction layer — swap engine by toggling the commented block.
|
|
3
|
-
* To use libsql: `bun add libsql`, comment bun:sqlite, uncomment libsql.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// ── bun:sqlite ─────────────────────────────────────────────────────
|
|
7
|
-
import { Database } from 'bun:sqlite'
|
|
8
|
-
export { Database }
|
|
9
|
-
|
|
10
|
-
// ── libsql (better-sqlite3 compatible, works on Node/Bun/Deno) ────
|
|
11
|
-
// import type LibsqlType from 'libsql'
|
|
12
|
-
// import LibsqlDatabase from 'libsql'
|
|
13
|
-
// export type Database = LibsqlType.Database
|
|
14
|
-
// export const Database: new (path: string, options?: LibsqlType.Options) => Database = LibsqlDatabase
|