kajji 0.1.0 → 0.2.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/README.md +33 -30
- package/bin/kajji +60 -0
- package/package.json +35 -55
- package/script/postinstall.mjs +50 -0
- package/bin/kajji.js +0 -2
- package/src/App.tsx +0 -229
- package/src/commander/bookmarks.ts +0 -129
- package/src/commander/diff.ts +0 -186
- package/src/commander/executor.ts +0 -285
- package/src/commander/files.ts +0 -87
- package/src/commander/log.ts +0 -99
- package/src/commander/operations.ts +0 -313
- package/src/commander/types.ts +0 -21
- package/src/components/AnsiText.tsx +0 -77
- package/src/components/BorderBox.tsx +0 -124
- package/src/components/FileTreeList.tsx +0 -105
- package/src/components/Layout.tsx +0 -48
- package/src/components/Panel.tsx +0 -143
- package/src/components/RevisionPicker.tsx +0 -165
- package/src/components/StatusBar.tsx +0 -158
- package/src/components/modals/BookmarkNameModal.tsx +0 -170
- package/src/components/modals/DescribeModal.tsx +0 -124
- package/src/components/modals/HelpModal.tsx +0 -372
- package/src/components/modals/RevisionPickerModal.tsx +0 -70
- package/src/components/modals/UndoModal.tsx +0 -75
- package/src/components/panels/BookmarksPanel.tsx +0 -768
- package/src/components/panels/CommandLogPanel.tsx +0 -40
- package/src/components/panels/LogPanel.tsx +0 -774
- package/src/components/panels/MainArea.tsx +0 -354
- package/src/context/command.tsx +0 -106
- package/src/context/commandlog.tsx +0 -45
- package/src/context/dialog.tsx +0 -217
- package/src/context/focus.tsx +0 -63
- package/src/context/helper.tsx +0 -24
- package/src/context/keybind.tsx +0 -51
- package/src/context/loading.tsx +0 -68
- package/src/context/sync.tsx +0 -868
- package/src/context/theme.tsx +0 -90
- package/src/context/types.ts +0 -51
- package/src/index.tsx +0 -15
- package/src/keybind/index.ts +0 -2
- package/src/keybind/parser.ts +0 -88
- package/src/keybind/types.ts +0 -83
- package/src/theme/index.ts +0 -3
- package/src/theme/presets/lazygit.ts +0 -45
- package/src/theme/presets/opencode.ts +0 -45
- package/src/theme/types.ts +0 -47
- package/src/utils/double-click.ts +0 -59
- package/src/utils/file-tree.ts +0 -154
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
A simple terminal UI for [Jujutsu](https://github.com/martinvonz/jj), inspired by [lazygit](https://github.com/jesseduffield/lazygit). Built with [OpenTUI](https://github.com/sst/opentui) and [SolidJS](https://www.solidjs.com/).
|
|
6
6
|
|
|
7
|
-

|
|
7
|
+

|
|
8
8
|
|
|
9
9
|
While learning jj I found myself coming back to lazygit to view diffs and traverse the changes I'd made quickly and easily, which has become increasingly important to me with the rise of coding agents. While there are existing jj TUIs, none quite scratched that lazygit itch.
|
|
10
10
|
|
|
@@ -20,35 +20,49 @@ Kajji is my attempt to bring the simplicity and polish of lazygit to jj, while a
|
|
|
20
20
|
|
|
21
21
|
## Features
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
23
|
+
**Core jj operations:**
|
|
24
|
+
- [x] View commit log with graph
|
|
25
|
+
- [x] View diffs (difftastic, delta, etc.)
|
|
26
|
+
- [x] New / edit / describe / squash / abandon
|
|
27
|
+
- [x] Undo / redo with preview
|
|
28
|
+
- [x] Bookmarks (create, delete, rename, move)
|
|
29
|
+
- [x] Git fetch / push
|
|
30
|
+
- [x] Operation log with restore
|
|
31
|
+
- [ ] Rebase
|
|
32
|
+
- [ ] Split
|
|
33
|
+
- [ ] Conflict resolution
|
|
34
|
+
|
|
35
|
+
**TUI polish:**
|
|
36
|
+
- [x] Vim-style navigation (j/k, ctrl+u/d)
|
|
37
|
+
- [x] Mouse support (click, double-click, scroll)
|
|
38
|
+
- [x] Collapsible file tree with status colors
|
|
39
|
+
- [x] Help palette with fuzzy search (`?`)
|
|
40
|
+
- [ ] Multi-select for batch operations
|
|
41
|
+
- [ ] Search and filter
|
|
31
42
|
|
|
32
43
|
## Installation
|
|
33
44
|
|
|
34
|
-
> **Requirements**: [
|
|
45
|
+
> **Requirements**: [jj](https://github.com/martinvonz/jj)
|
|
35
46
|
|
|
36
47
|
```bash
|
|
37
|
-
#
|
|
38
|
-
|
|
48
|
+
# recommended (standalone binary, no dependencies)
|
|
49
|
+
curl -fsSL https://raw.githubusercontent.com/eliaskc/kajji/main/install.sh | bash
|
|
39
50
|
|
|
40
|
-
#
|
|
51
|
+
# or via package manager
|
|
52
|
+
npm install -g kajji
|
|
41
53
|
bun install -g kajji
|
|
42
|
-
|
|
43
|
-
# pnpm
|
|
44
54
|
pnpm add -g kajji
|
|
55
|
+
yarn global add kajji
|
|
45
56
|
|
|
46
|
-
# or run directly
|
|
57
|
+
# or run directly without installing
|
|
58
|
+
npx kajji
|
|
47
59
|
bunx kajji
|
|
48
60
|
```
|
|
49
61
|
|
|
50
62
|
### From source
|
|
51
63
|
|
|
64
|
+
> **Requirements**: [Bun](https://bun.sh)
|
|
65
|
+
|
|
52
66
|
```bash
|
|
53
67
|
git clone https://github.com/eliaskc/kajji.git
|
|
54
68
|
cd kajji
|
|
@@ -95,21 +109,10 @@ kajji
|
|
|
95
109
|
| `c` | Create bookmark |
|
|
96
110
|
| `d` | Delete bookmark |
|
|
97
111
|
| `r` | Rename bookmark |
|
|
98
|
-
| `b` |
|
|
99
|
-
|
|
100
|
-
## Next up
|
|
101
|
-
|
|
102
|
-
- Multi-select for batch rebase and squash
|
|
103
|
-
- Search and filter (log, bookmarks, files)
|
|
104
|
-
- Workspaces tab (monitor agent commits across workspaces)
|
|
105
|
-
|
|
106
|
-
## Exploring
|
|
107
|
-
|
|
108
|
-
- Interactive `jj split` (file/hunk selection)
|
|
109
|
-
- Stacked PR creation and overview
|
|
110
|
-
- Configuration (user config file, theme switching)
|
|
112
|
+
| `b` | Set bookmark on commit |
|
|
113
|
+
| `m` | Move bookmark |
|
|
111
114
|
|
|
112
|
-
See [PROJECT](./context/PROJECT.md) for the full
|
|
115
|
+
See [PROJECT](./context/PROJECT.md) for the full roadmap.
|
|
113
116
|
|
|
114
117
|
## Built With
|
|
115
118
|
|
package/bin/kajji
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const childProcess = require("child_process")
|
|
4
|
+
const fs = require("fs")
|
|
5
|
+
const path = require("path")
|
|
6
|
+
const os = require("os")
|
|
7
|
+
|
|
8
|
+
function run(target) {
|
|
9
|
+
const result = childProcess.spawnSync(target, process.argv.slice(2), {
|
|
10
|
+
stdio: "inherit",
|
|
11
|
+
})
|
|
12
|
+
if (result.error) {
|
|
13
|
+
console.error(result.error.message)
|
|
14
|
+
process.exit(1)
|
|
15
|
+
}
|
|
16
|
+
process.exit(typeof result.status === "number" ? result.status : 0)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const envPath = process.env.KAJJI_BIN_PATH
|
|
20
|
+
if (envPath) {
|
|
21
|
+
run(envPath)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const scriptPath = fs.realpathSync(__filename)
|
|
25
|
+
const scriptDir = path.dirname(scriptPath)
|
|
26
|
+
|
|
27
|
+
const platformMap = { darwin: "darwin", linux: "linux" }
|
|
28
|
+
const archMap = { x64: "x64", arm64: "arm64" }
|
|
29
|
+
|
|
30
|
+
const platform = platformMap[os.platform()] || os.platform()
|
|
31
|
+
const arch = archMap[os.arch()] || os.arch()
|
|
32
|
+
const base = "kajji-" + platform + "-" + arch
|
|
33
|
+
|
|
34
|
+
function findBinary(startDir) {
|
|
35
|
+
let current = startDir
|
|
36
|
+
for (;;) {
|
|
37
|
+
const modules = path.join(current, "node_modules")
|
|
38
|
+
if (fs.existsSync(modules)) {
|
|
39
|
+
for (const entry of fs.readdirSync(modules)) {
|
|
40
|
+
if (!entry.startsWith(base)) continue
|
|
41
|
+
const candidate = path.join(modules, entry, "bin", "kajji")
|
|
42
|
+
if (fs.existsSync(candidate)) return candidate
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const parent = path.dirname(current)
|
|
46
|
+
if (parent === current) return
|
|
47
|
+
current = parent
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const resolved = findBinary(scriptDir)
|
|
52
|
+
if (!resolved) {
|
|
53
|
+
console.error(
|
|
54
|
+
`Could not find kajji binary for your platform (${platform}-${arch}).\n` +
|
|
55
|
+
`Try installing the platform package: npm install ${base}`
|
|
56
|
+
)
|
|
57
|
+
process.exit(1)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
run(resolved)
|
package/package.json
CHANGED
|
@@ -1,56 +1,36 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"keywords": [
|
|
38
|
-
"jj",
|
|
39
|
-
"jujutsu",
|
|
40
|
-
"tui",
|
|
41
|
-
"terminal",
|
|
42
|
-
"cli",
|
|
43
|
-
"vcs",
|
|
44
|
-
"version-control"
|
|
45
|
-
],
|
|
46
|
-
"repository": {
|
|
47
|
-
"type": "git",
|
|
48
|
-
"url": "git+https://github.com/eliaskc/kajji.git"
|
|
49
|
-
},
|
|
50
|
-
"homepage": "https://github.com/eliaskc/kajji#readme",
|
|
51
|
-
"bugs": {
|
|
52
|
-
"url": "https://github.com/eliaskc/kajji/issues"
|
|
53
|
-
},
|
|
54
|
-
"author": "eliaskc",
|
|
55
|
-
"license": "MIT"
|
|
56
|
-
}
|
|
2
|
+
"name": "kajji",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A terminal UI for Jujutsu: the rudder for your jj",
|
|
5
|
+
"bin": {
|
|
6
|
+
"kajji": "./bin/kajji"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"postinstall": "node ./script/postinstall.mjs"
|
|
10
|
+
},
|
|
11
|
+
"optionalDependencies": {
|
|
12
|
+
"kajji-darwin-arm64": "0.2.0",
|
|
13
|
+
"kajji-darwin-x64": "0.2.0",
|
|
14
|
+
"kajji-linux-x64": "0.2.0",
|
|
15
|
+
"kajji-linux-arm64": "0.2.0"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/eliaskc/kajji.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/eliaskc/kajji#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/eliaskc/kajji/issues"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"jj",
|
|
27
|
+
"jujutsu",
|
|
28
|
+
"tui",
|
|
29
|
+
"terminal",
|
|
30
|
+
"cli",
|
|
31
|
+
"vcs",
|
|
32
|
+
"version-control"
|
|
33
|
+
],
|
|
34
|
+
"author": "eliaskc",
|
|
35
|
+
"license": "MIT"
|
|
36
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs"
|
|
4
|
+
import os from "node:os"
|
|
5
|
+
import path from "node:path"
|
|
6
|
+
import { fileURLToPath } from "node:url"
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
|
|
10
|
+
const platformMap = { darwin: "darwin", linux: "linux" }
|
|
11
|
+
const archMap = { x64: "x64", arm64: "arm64" }
|
|
12
|
+
|
|
13
|
+
const platform = platformMap[os.platform()]
|
|
14
|
+
const arch = archMap[os.arch()]
|
|
15
|
+
|
|
16
|
+
if (!platform || !arch) {
|
|
17
|
+
console.log(`Unsupported platform: ${os.platform()}-${os.arch()}`)
|
|
18
|
+
process.exit(0)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const packageName = `kajji-${platform}-${arch}`
|
|
22
|
+
|
|
23
|
+
let packageDir
|
|
24
|
+
try {
|
|
25
|
+
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
|
|
26
|
+
paths: [path.join(__dirname, "..")],
|
|
27
|
+
})
|
|
28
|
+
packageDir = path.dirname(packageJsonPath)
|
|
29
|
+
} catch {
|
|
30
|
+
console.log(`Platform package ${packageName} not found, skipping symlink`)
|
|
31
|
+
process.exit(0)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const binaryPath = path.join(packageDir, "bin", "kajji")
|
|
35
|
+
const targetPath = path.join(__dirname, "..", "bin", "kajji-binary")
|
|
36
|
+
|
|
37
|
+
if (!fs.existsSync(binaryPath)) {
|
|
38
|
+
console.log(`Binary not found at ${binaryPath}`)
|
|
39
|
+
process.exit(0)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
if (fs.existsSync(targetPath)) {
|
|
44
|
+
fs.unlinkSync(targetPath)
|
|
45
|
+
}
|
|
46
|
+
fs.symlinkSync(binaryPath, targetPath)
|
|
47
|
+
console.log(`kajji binary linked: ${targetPath} -> ${binaryPath}`)
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.log(`Could not create symlink: ${err.message}`)
|
|
50
|
+
}
|
package/bin/kajji.js
DELETED
package/src/App.tsx
DELETED
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import { useRenderer } from "@opentui/solid"
|
|
2
|
-
import { onMount } from "solid-js"
|
|
3
|
-
import { jjGitFetch, jjGitPush } from "./commander/operations"
|
|
4
|
-
import { Layout } from "./components/Layout"
|
|
5
|
-
import { HelpModal } from "./components/modals/HelpModal"
|
|
6
|
-
import { BookmarksPanel } from "./components/panels/BookmarksPanel"
|
|
7
|
-
import { LogPanel } from "./components/panels/LogPanel"
|
|
8
|
-
import { MainArea } from "./components/panels/MainArea"
|
|
9
|
-
import { CommandProvider, useCommand } from "./context/command"
|
|
10
|
-
import { CommandLogProvider, useCommandLog } from "./context/commandlog"
|
|
11
|
-
import { DialogContainer, DialogProvider, useDialog } from "./context/dialog"
|
|
12
|
-
import { FocusProvider, useFocus } from "./context/focus"
|
|
13
|
-
import { KeybindProvider } from "./context/keybind"
|
|
14
|
-
import { LoadingProvider, useLoading } from "./context/loading"
|
|
15
|
-
import { SyncProvider, useSync } from "./context/sync"
|
|
16
|
-
import { ThemeProvider } from "./context/theme"
|
|
17
|
-
|
|
18
|
-
function AppContent() {
|
|
19
|
-
const renderer = useRenderer()
|
|
20
|
-
const { loadLog, loadBookmarks, refresh } = useSync()
|
|
21
|
-
const focus = useFocus()
|
|
22
|
-
const command = useCommand()
|
|
23
|
-
const dialog = useDialog()
|
|
24
|
-
const commandLog = useCommandLog()
|
|
25
|
-
const globalLoading = useLoading()
|
|
26
|
-
|
|
27
|
-
onMount(() => {
|
|
28
|
-
loadLog()
|
|
29
|
-
loadBookmarks()
|
|
30
|
-
|
|
31
|
-
renderer.console.keyBindings = [
|
|
32
|
-
{ name: "y", ctrl: true, action: "copy-selection" },
|
|
33
|
-
]
|
|
34
|
-
renderer.console.onCopySelection = (text) => {
|
|
35
|
-
const proc = Bun.spawn(["pbcopy"], { stdin: "pipe" })
|
|
36
|
-
proc.stdin.write(text)
|
|
37
|
-
proc.stdin.end()
|
|
38
|
-
}
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
command.register(() => [
|
|
42
|
-
{
|
|
43
|
-
id: "global.quit",
|
|
44
|
-
title: "quit",
|
|
45
|
-
keybind: "quit",
|
|
46
|
-
context: "global",
|
|
47
|
-
type: "action",
|
|
48
|
-
onSelect: () => {
|
|
49
|
-
renderer.destroy()
|
|
50
|
-
process.exit(0)
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
...(Bun.env.NODE_ENV === "development"
|
|
54
|
-
? [
|
|
55
|
-
{
|
|
56
|
-
id: "global.toggle_console",
|
|
57
|
-
title: "toggle console",
|
|
58
|
-
keybind: "toggle_console" as const,
|
|
59
|
-
context: "global" as const,
|
|
60
|
-
type: "action" as const,
|
|
61
|
-
onSelect: () => renderer.console.toggle(),
|
|
62
|
-
},
|
|
63
|
-
]
|
|
64
|
-
: []),
|
|
65
|
-
{
|
|
66
|
-
id: "global.focus_next",
|
|
67
|
-
title: "focus next panel",
|
|
68
|
-
keybind: "focus_next",
|
|
69
|
-
context: "global",
|
|
70
|
-
type: "navigation",
|
|
71
|
-
visibility: "help-only",
|
|
72
|
-
onSelect: () => focus.cycleNext(),
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
id: "global.focus_prev",
|
|
76
|
-
title: "focus previous panel",
|
|
77
|
-
keybind: "focus_prev",
|
|
78
|
-
context: "global",
|
|
79
|
-
type: "navigation",
|
|
80
|
-
visibility: "help-only",
|
|
81
|
-
onSelect: () => focus.cyclePrev(),
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
id: "global.focus_panel_1",
|
|
85
|
-
title: "focus log panel",
|
|
86
|
-
keybind: "focus_panel_1",
|
|
87
|
-
context: "global",
|
|
88
|
-
type: "navigation",
|
|
89
|
-
visibility: "help-only",
|
|
90
|
-
onSelect: () => focus.setPanel("log"),
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
id: "global.focus_panel_2",
|
|
94
|
-
title: "focus refs panel",
|
|
95
|
-
keybind: "focus_panel_2",
|
|
96
|
-
context: "global",
|
|
97
|
-
type: "navigation",
|
|
98
|
-
visibility: "help-only",
|
|
99
|
-
onSelect: () => focus.setPanel("refs"),
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
id: "global.focus_panel_3",
|
|
103
|
-
title: "focus detail panel",
|
|
104
|
-
keybind: "focus_panel_3",
|
|
105
|
-
context: "global",
|
|
106
|
-
type: "navigation",
|
|
107
|
-
visibility: "help-only",
|
|
108
|
-
onSelect: () => focus.setPanel("detail"),
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
id: "global.help",
|
|
112
|
-
title: "help",
|
|
113
|
-
keybind: "help",
|
|
114
|
-
context: "global",
|
|
115
|
-
type: "action",
|
|
116
|
-
onSelect: () =>
|
|
117
|
-
dialog.toggle("help", () => <HelpModal />, {
|
|
118
|
-
hints: [{ key: "enter", label: "execute" }],
|
|
119
|
-
}),
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
id: "global.refresh",
|
|
123
|
-
title: "refresh",
|
|
124
|
-
keybind: "refresh",
|
|
125
|
-
context: "global",
|
|
126
|
-
type: "action",
|
|
127
|
-
visibility: "help-only",
|
|
128
|
-
onSelect: () => refresh(),
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
id: "global.git_fetch",
|
|
132
|
-
title: "git fetch",
|
|
133
|
-
keybind: "jj_git_fetch",
|
|
134
|
-
context: "global",
|
|
135
|
-
type: "git",
|
|
136
|
-
visibility: "help-only",
|
|
137
|
-
onSelect: async () => {
|
|
138
|
-
const result = await globalLoading.run("Fetching...", () =>
|
|
139
|
-
jjGitFetch(),
|
|
140
|
-
)
|
|
141
|
-
commandLog.addEntry(result)
|
|
142
|
-
if (result.success) {
|
|
143
|
-
refresh()
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
id: "global.git_fetch_all",
|
|
149
|
-
title: "git fetch all",
|
|
150
|
-
keybind: "jj_git_fetch_all",
|
|
151
|
-
context: "global",
|
|
152
|
-
type: "git",
|
|
153
|
-
visibility: "help-only",
|
|
154
|
-
onSelect: async () => {
|
|
155
|
-
const result = await globalLoading.run("Fetching all...", () =>
|
|
156
|
-
jjGitFetch({ allRemotes: true }),
|
|
157
|
-
)
|
|
158
|
-
commandLog.addEntry(result)
|
|
159
|
-
if (result.success) {
|
|
160
|
-
refresh()
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
id: "global.git_push",
|
|
166
|
-
title: "git push",
|
|
167
|
-
keybind: "jj_git_push",
|
|
168
|
-
context: "global",
|
|
169
|
-
type: "git",
|
|
170
|
-
visibility: "help-only",
|
|
171
|
-
onSelect: async () => {
|
|
172
|
-
const result = await globalLoading.run("Pushing...", () => jjGitPush())
|
|
173
|
-
commandLog.addEntry(result)
|
|
174
|
-
if (result.success) {
|
|
175
|
-
refresh()
|
|
176
|
-
}
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
id: "global.git_push_all",
|
|
181
|
-
title: "git push all",
|
|
182
|
-
keybind: "jj_git_push_all",
|
|
183
|
-
context: "global",
|
|
184
|
-
type: "git",
|
|
185
|
-
visibility: "help-only",
|
|
186
|
-
onSelect: async () => {
|
|
187
|
-
const result = await globalLoading.run("Pushing all...", () =>
|
|
188
|
-
jjGitPush({ all: true }),
|
|
189
|
-
)
|
|
190
|
-
commandLog.addEntry(result)
|
|
191
|
-
if (result.success) {
|
|
192
|
-
refresh()
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
])
|
|
197
|
-
|
|
198
|
-
return (
|
|
199
|
-
<DialogContainer>
|
|
200
|
-
<Layout
|
|
201
|
-
top={<LogPanel />}
|
|
202
|
-
bottom={<BookmarksPanel />}
|
|
203
|
-
right={<MainArea />}
|
|
204
|
-
/>
|
|
205
|
-
</DialogContainer>
|
|
206
|
-
)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export function App() {
|
|
210
|
-
return (
|
|
211
|
-
<ThemeProvider>
|
|
212
|
-
<FocusProvider>
|
|
213
|
-
<LoadingProvider>
|
|
214
|
-
<SyncProvider>
|
|
215
|
-
<KeybindProvider>
|
|
216
|
-
<CommandLogProvider>
|
|
217
|
-
<DialogProvider>
|
|
218
|
-
<CommandProvider>
|
|
219
|
-
<AppContent />
|
|
220
|
-
</CommandProvider>
|
|
221
|
-
</DialogProvider>
|
|
222
|
-
</CommandLogProvider>
|
|
223
|
-
</KeybindProvider>
|
|
224
|
-
</SyncProvider>
|
|
225
|
-
</LoadingProvider>
|
|
226
|
-
</FocusProvider>
|
|
227
|
-
</ThemeProvider>
|
|
228
|
-
)
|
|
229
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { execute } from "./executor"
|
|
2
|
-
import type { OperationResult } from "./operations"
|
|
3
|
-
|
|
4
|
-
export interface Bookmark {
|
|
5
|
-
name: string
|
|
6
|
-
changeId: string
|
|
7
|
-
commitId: string
|
|
8
|
-
description: string
|
|
9
|
-
isLocal: boolean
|
|
10
|
-
remote?: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface FetchBookmarksOptions {
|
|
14
|
-
cwd?: string
|
|
15
|
-
allRemotes?: boolean
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function fetchBookmarks(
|
|
19
|
-
options: FetchBookmarksOptions = {},
|
|
20
|
-
): Promise<Bookmark[]> {
|
|
21
|
-
const args = ["bookmark", "list"]
|
|
22
|
-
|
|
23
|
-
if (options.allRemotes) {
|
|
24
|
-
args.push("--all-remotes")
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const result = await execute(args, { cwd: options.cwd })
|
|
28
|
-
|
|
29
|
-
if (!result.success) {
|
|
30
|
-
throw new Error(`jj bookmark list failed: ${result.stderr}`)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return parseBookmarkOutput(result.stdout)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function parseBookmarkOutput(output: string): Bookmark[] {
|
|
37
|
-
const bookmarks: Bookmark[] = []
|
|
38
|
-
const lines = output.split("\n")
|
|
39
|
-
|
|
40
|
-
for (const line of lines) {
|
|
41
|
-
if (!line.trim()) continue
|
|
42
|
-
|
|
43
|
-
const isRemote = line.startsWith(" @")
|
|
44
|
-
|
|
45
|
-
if (isRemote) {
|
|
46
|
-
const match = line.match(/^\s+@(\S+):\s+(\S+)\s+(\S+)\s*(.*)$/)
|
|
47
|
-
if (match) {
|
|
48
|
-
bookmarks.push({
|
|
49
|
-
name: match[1] ?? "",
|
|
50
|
-
changeId: match[2] ?? "",
|
|
51
|
-
commitId: match[3] ?? "",
|
|
52
|
-
description: match[4]?.trim() ?? "",
|
|
53
|
-
isLocal: false,
|
|
54
|
-
remote: match[1],
|
|
55
|
-
})
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
const match = line.match(/^(\S+):\s+(\S+)\s+(\S+)\s*(.*)$/)
|
|
59
|
-
if (match) {
|
|
60
|
-
bookmarks.push({
|
|
61
|
-
name: match[1] ?? "",
|
|
62
|
-
changeId: match[2] ?? "",
|
|
63
|
-
commitId: match[3] ?? "",
|
|
64
|
-
description: match[4]?.trim() ?? "",
|
|
65
|
-
isLocal: true,
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return bookmarks
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export async function jjBookmarkCreate(
|
|
75
|
-
name: string,
|
|
76
|
-
options?: { revision?: string },
|
|
77
|
-
): Promise<OperationResult> {
|
|
78
|
-
const args = ["bookmark", "create", name]
|
|
79
|
-
if (options?.revision) {
|
|
80
|
-
args.push("-r", options.revision)
|
|
81
|
-
}
|
|
82
|
-
const result = await execute(args)
|
|
83
|
-
return {
|
|
84
|
-
...result,
|
|
85
|
-
command: `jj ${args.join(" ")}`,
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export async function jjBookmarkDelete(name: string): Promise<OperationResult> {
|
|
90
|
-
const args = ["bookmark", "delete", name]
|
|
91
|
-
const result = await execute(args)
|
|
92
|
-
return {
|
|
93
|
-
...result,
|
|
94
|
-
command: `jj ${args.join(" ")}`,
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export async function jjBookmarkRename(
|
|
99
|
-
oldName: string,
|
|
100
|
-
newName: string,
|
|
101
|
-
): Promise<OperationResult> {
|
|
102
|
-
const args = ["bookmark", "rename", oldName, newName]
|
|
103
|
-
const result = await execute(args)
|
|
104
|
-
return {
|
|
105
|
-
...result,
|
|
106
|
-
command: `jj ${args.join(" ")}`,
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export async function jjBookmarkForget(name: string): Promise<OperationResult> {
|
|
111
|
-
const args = ["bookmark", "forget", name]
|
|
112
|
-
const result = await execute(args)
|
|
113
|
-
return {
|
|
114
|
-
...result,
|
|
115
|
-
command: `jj ${args.join(" ")}`,
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export async function jjBookmarkSet(
|
|
120
|
-
name: string,
|
|
121
|
-
revision: string,
|
|
122
|
-
): Promise<OperationResult> {
|
|
123
|
-
const args = ["bookmark", "set", name, "-r", revision]
|
|
124
|
-
const result = await execute(args)
|
|
125
|
-
return {
|
|
126
|
-
...result,
|
|
127
|
-
command: `jj ${args.join(" ")}`,
|
|
128
|
-
}
|
|
129
|
-
}
|