kajji 0.1.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/LICENSE +21 -0
- package/README.md +128 -0
- package/bin/kajji.js +2 -0
- package/package.json +56 -0
- package/src/App.tsx +229 -0
- package/src/commander/bookmarks.ts +129 -0
- package/src/commander/diff.ts +186 -0
- package/src/commander/executor.ts +285 -0
- package/src/commander/files.ts +87 -0
- package/src/commander/log.ts +99 -0
- package/src/commander/operations.ts +313 -0
- package/src/commander/types.ts +21 -0
- package/src/components/AnsiText.tsx +77 -0
- package/src/components/BorderBox.tsx +124 -0
- package/src/components/FileTreeList.tsx +105 -0
- package/src/components/Layout.tsx +48 -0
- package/src/components/Panel.tsx +143 -0
- package/src/components/RevisionPicker.tsx +165 -0
- package/src/components/StatusBar.tsx +158 -0
- package/src/components/modals/BookmarkNameModal.tsx +170 -0
- package/src/components/modals/DescribeModal.tsx +124 -0
- package/src/components/modals/HelpModal.tsx +372 -0
- package/src/components/modals/RevisionPickerModal.tsx +70 -0
- package/src/components/modals/UndoModal.tsx +75 -0
- package/src/components/panels/BookmarksPanel.tsx +768 -0
- package/src/components/panels/CommandLogPanel.tsx +40 -0
- package/src/components/panels/LogPanel.tsx +774 -0
- package/src/components/panels/MainArea.tsx +354 -0
- package/src/context/command.tsx +106 -0
- package/src/context/commandlog.tsx +45 -0
- package/src/context/dialog.tsx +217 -0
- package/src/context/focus.tsx +63 -0
- package/src/context/helper.tsx +24 -0
- package/src/context/keybind.tsx +51 -0
- package/src/context/loading.tsx +68 -0
- package/src/context/sync.tsx +868 -0
- package/src/context/theme.tsx +90 -0
- package/src/context/types.ts +51 -0
- package/src/index.tsx +15 -0
- package/src/keybind/index.ts +2 -0
- package/src/keybind/parser.ts +88 -0
- package/src/keybind/types.ts +83 -0
- package/src/theme/index.ts +3 -0
- package/src/theme/presets/lazygit.ts +45 -0
- package/src/theme/presets/opencode.ts +45 -0
- package/src/theme/types.ts +47 -0
- package/src/utils/double-click.ts +59 -0
- package/src/utils/file-tree.ts +154 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createMemo, createSignal } from "solid-js"
|
|
2
|
+
import { createSimpleContext } from "./helper"
|
|
3
|
+
import { type Context, type Panel, panelFromContext } from "./types"
|
|
4
|
+
|
|
5
|
+
export type { Panel }
|
|
6
|
+
|
|
7
|
+
const PANEL_ORDER: Panel[] = ["log", "refs", "detail"]
|
|
8
|
+
|
|
9
|
+
export const { use: useFocus, provider: FocusProvider } = createSimpleContext({
|
|
10
|
+
name: "Focus",
|
|
11
|
+
init: () => {
|
|
12
|
+
const [activeContext, setActiveContext] =
|
|
13
|
+
createSignal<Context>("log.revisions")
|
|
14
|
+
|
|
15
|
+
const panel = createMemo<Panel>(() => {
|
|
16
|
+
return panelFromContext(activeContext()) ?? "log"
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const setPanel = (p: Panel) => {
|
|
20
|
+
const current = activeContext()
|
|
21
|
+
const currentPanel = panelFromContext(current)
|
|
22
|
+
if (currentPanel === p) return
|
|
23
|
+
|
|
24
|
+
switch (p) {
|
|
25
|
+
case "log":
|
|
26
|
+
setActiveContext("log.revisions")
|
|
27
|
+
break
|
|
28
|
+
case "refs":
|
|
29
|
+
setActiveContext("refs.bookmarks")
|
|
30
|
+
break
|
|
31
|
+
case "detail":
|
|
32
|
+
setActiveContext("detail")
|
|
33
|
+
break
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const cycleNext = () => {
|
|
38
|
+
const current = panel()
|
|
39
|
+
const idx = PANEL_ORDER.indexOf(current)
|
|
40
|
+
const next = PANEL_ORDER[(idx + 1) % PANEL_ORDER.length] ?? "log"
|
|
41
|
+
setPanel(next)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const cyclePrev = () => {
|
|
45
|
+
const current = panel()
|
|
46
|
+
const idx = PANEL_ORDER.indexOf(current)
|
|
47
|
+
const next =
|
|
48
|
+
PANEL_ORDER[(idx - 1 + PANEL_ORDER.length) % PANEL_ORDER.length] ??
|
|
49
|
+
"log"
|
|
50
|
+
setPanel(next)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
panel,
|
|
55
|
+
setPanel,
|
|
56
|
+
activeContext,
|
|
57
|
+
setActiveContext,
|
|
58
|
+
cycleNext,
|
|
59
|
+
cyclePrev,
|
|
60
|
+
isPanel: (p: Panel) => panel() === p,
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ParentProps, createContext, useContext } from "solid-js"
|
|
2
|
+
|
|
3
|
+
export function createSimpleContext<T>(input: {
|
|
4
|
+
name: string
|
|
5
|
+
init: () => T
|
|
6
|
+
}) {
|
|
7
|
+
const ctx = createContext<T>()
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
provider: (props: ParentProps) => {
|
|
11
|
+
const value = input.init()
|
|
12
|
+
return <ctx.Provider value={value}>{props.children}</ctx.Provider>
|
|
13
|
+
},
|
|
14
|
+
use: () => {
|
|
15
|
+
const value = useContext(ctx)
|
|
16
|
+
if (!value) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`use${input.name} must be used within ${input.name}Provider`,
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
return value
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createMemo } from "solid-js"
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_KEYBINDS,
|
|
4
|
+
type KeybindConfigKey,
|
|
5
|
+
type KeybindInfo,
|
|
6
|
+
fromParsedKey,
|
|
7
|
+
keybindToString,
|
|
8
|
+
match,
|
|
9
|
+
parse,
|
|
10
|
+
} from "../keybind"
|
|
11
|
+
import { createSimpleContext } from "./helper"
|
|
12
|
+
|
|
13
|
+
export const { use: useKeybind, provider: KeybindProvider } =
|
|
14
|
+
createSimpleContext({
|
|
15
|
+
name: "Keybind",
|
|
16
|
+
init: () => {
|
|
17
|
+
const keybinds = createMemo(() => {
|
|
18
|
+
const parsed: Record<string, KeybindInfo[]> = {}
|
|
19
|
+
for (const [key, value] of Object.entries(DEFAULT_KEYBINDS)) {
|
|
20
|
+
parsed[key] = parse(value)
|
|
21
|
+
}
|
|
22
|
+
return parsed
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
match: (
|
|
27
|
+
configKey: KeybindConfigKey,
|
|
28
|
+
evt: {
|
|
29
|
+
name?: string
|
|
30
|
+
ctrl?: boolean
|
|
31
|
+
meta?: boolean
|
|
32
|
+
shift?: boolean
|
|
33
|
+
},
|
|
34
|
+
): boolean => {
|
|
35
|
+
const bindings = keybinds()[configKey]
|
|
36
|
+
if (!bindings) return false
|
|
37
|
+
const parsed = fromParsedKey(evt)
|
|
38
|
+
return bindings.some((binding) => match(binding, parsed))
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
print: (configKey: KeybindConfigKey): string => {
|
|
42
|
+
const bindings = keybinds()[configKey]
|
|
43
|
+
const first = bindings?.[0]
|
|
44
|
+
if (!first) return ""
|
|
45
|
+
return keybindToString(first)
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
all: () => DEFAULT_KEYBINDS,
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type JSX, createContext, createSignal, useContext } from "solid-js"
|
|
2
|
+
|
|
3
|
+
const DEBOUNCE_MS = 150
|
|
4
|
+
|
|
5
|
+
interface LoadingContextValue {
|
|
6
|
+
isLoading: () => boolean
|
|
7
|
+
loadingText: () => string | null
|
|
8
|
+
start: (text: string) => void
|
|
9
|
+
stop: () => void
|
|
10
|
+
run: <T>(text: string, fn: () => Promise<T>) => Promise<T>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const LoadingContext = createContext<LoadingContextValue>()
|
|
14
|
+
|
|
15
|
+
export function LoadingProvider(props: { children: JSX.Element }) {
|
|
16
|
+
const [isLoading, setIsLoading] = createSignal(false)
|
|
17
|
+
const [loadingText, setLoadingText] = createSignal<string | null>(null)
|
|
18
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
|
19
|
+
|
|
20
|
+
const start = (text: string) => {
|
|
21
|
+
setLoadingText(text)
|
|
22
|
+
if (debounceTimer) clearTimeout(debounceTimer)
|
|
23
|
+
debounceTimer = setTimeout(() => {
|
|
24
|
+
setIsLoading(true)
|
|
25
|
+
debounceTimer = null
|
|
26
|
+
}, DEBOUNCE_MS)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const stop = () => {
|
|
30
|
+
if (debounceTimer) {
|
|
31
|
+
clearTimeout(debounceTimer)
|
|
32
|
+
debounceTimer = null
|
|
33
|
+
}
|
|
34
|
+
setIsLoading(false)
|
|
35
|
+
setLoadingText(null)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const run = async <T,>(text: string, fn: () => Promise<T>): Promise<T> => {
|
|
39
|
+
start(text)
|
|
40
|
+
try {
|
|
41
|
+
return await fn()
|
|
42
|
+
} finally {
|
|
43
|
+
stop()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const value: LoadingContextValue = {
|
|
48
|
+
isLoading,
|
|
49
|
+
loadingText,
|
|
50
|
+
start,
|
|
51
|
+
stop,
|
|
52
|
+
run,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<LoadingContext.Provider value={value}>
|
|
57
|
+
{props.children}
|
|
58
|
+
</LoadingContext.Provider>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function useLoading(): LoadingContextValue {
|
|
63
|
+
const ctx = useContext(LoadingContext)
|
|
64
|
+
if (!ctx) {
|
|
65
|
+
throw new Error("useLoading must be used within LoadingProvider")
|
|
66
|
+
}
|
|
67
|
+
return ctx
|
|
68
|
+
}
|