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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +128 -0
  3. package/bin/kajji.js +2 -0
  4. package/package.json +56 -0
  5. package/src/App.tsx +229 -0
  6. package/src/commander/bookmarks.ts +129 -0
  7. package/src/commander/diff.ts +186 -0
  8. package/src/commander/executor.ts +285 -0
  9. package/src/commander/files.ts +87 -0
  10. package/src/commander/log.ts +99 -0
  11. package/src/commander/operations.ts +313 -0
  12. package/src/commander/types.ts +21 -0
  13. package/src/components/AnsiText.tsx +77 -0
  14. package/src/components/BorderBox.tsx +124 -0
  15. package/src/components/FileTreeList.tsx +105 -0
  16. package/src/components/Layout.tsx +48 -0
  17. package/src/components/Panel.tsx +143 -0
  18. package/src/components/RevisionPicker.tsx +165 -0
  19. package/src/components/StatusBar.tsx +158 -0
  20. package/src/components/modals/BookmarkNameModal.tsx +170 -0
  21. package/src/components/modals/DescribeModal.tsx +124 -0
  22. package/src/components/modals/HelpModal.tsx +372 -0
  23. package/src/components/modals/RevisionPickerModal.tsx +70 -0
  24. package/src/components/modals/UndoModal.tsx +75 -0
  25. package/src/components/panels/BookmarksPanel.tsx +768 -0
  26. package/src/components/panels/CommandLogPanel.tsx +40 -0
  27. package/src/components/panels/LogPanel.tsx +774 -0
  28. package/src/components/panels/MainArea.tsx +354 -0
  29. package/src/context/command.tsx +106 -0
  30. package/src/context/commandlog.tsx +45 -0
  31. package/src/context/dialog.tsx +217 -0
  32. package/src/context/focus.tsx +63 -0
  33. package/src/context/helper.tsx +24 -0
  34. package/src/context/keybind.tsx +51 -0
  35. package/src/context/loading.tsx +68 -0
  36. package/src/context/sync.tsx +868 -0
  37. package/src/context/theme.tsx +90 -0
  38. package/src/context/types.ts +51 -0
  39. package/src/index.tsx +15 -0
  40. package/src/keybind/index.ts +2 -0
  41. package/src/keybind/parser.ts +88 -0
  42. package/src/keybind/types.ts +83 -0
  43. package/src/theme/index.ts +3 -0
  44. package/src/theme/presets/lazygit.ts +45 -0
  45. package/src/theme/presets/opencode.ts +45 -0
  46. package/src/theme/types.ts +47 -0
  47. package/src/utils/double-click.ts +59 -0
  48. 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
+ }