Compare commits

18 Commits

Author SHA1 Message Date
890243d7a3 add mouse support
SCIP Index / index (push) Successful in 1m33s
2026-03-09 23:13:31 -04:00
c69c254512 Update .github/workflows/scip-index.yml
SCIP Index / index (push) Successful in 3m32s
2026-02-04 05:44:12 -10:00
6378c4910b Update .github/workflows/scip-index.yml
SCIP Index / index (push) Failing after 6s
2026-02-04 05:43:22 -10:00
0c5d69e7b2 Update .github/workflows/scip-index.yml
SCIP Index / index (push) Failing after 9s
2026-02-04 05:41:49 -10:00
9940d53f79 Update .github/workflows/scip-index.yml
SCIP Index / index (push) Failing after 3s
2026-02-04 05:37:21 -10:00
c814f3618a Update .github/workflows/scip-index.yml
SCIP Index / index (push) Failing after 9s
2026-02-04 05:34:49 -10:00
1ec1171c9b Delete file1.txt
SCIP Index / index (push) Successful in 1m35s
2026-02-03 22:07:47 -10:00
b3cd896fd5 Index SCIP for all branches, not just main/master
SCIP Index / index (push) Successful in 1m42s
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 21:50:29 -10:00
1858f3e5d0 Add package-map for cross-repo SCIP navigation
SCIP Index / index (push) Successful in 1m37s
- Add package-map.edn mapping namespaces to their packages
- Update CI workflow to pass package-map when generating SCIP index
- Enables cross-repo navigation to clojure-tui in Sourcegraph

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 23:10:04 -05:00
f77bf3bbfc Trigger CI for cross-repo fix
SCIP Index / index (push) Successful in 1m41s
2026-02-03 19:02:43 -05:00
e80dfdf8e9 Trigger SCIP reindex for :refer fix
SCIP Index / index (push) Successful in 1m34s
2026-02-03 18:31:39 -05:00
51ba410f15 Fix CI: clone clojure-tui local dependency
SCIP Index / index (push) Successful in 1m46s
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 17:58:35 -05:00
e7c975fdea Fix CI: install Babashka for bb.edn projects
SCIP Index / index (push) Failing after 37s
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 17:54:09 -05:00
f51f8b743d Add SCIP indexing CI workflow for Sourcegraph
SCIP Index / index (push) Failing after 23s
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 17:50:18 -05:00
785d07b08f Add generated files to gitignore
Ignore clj-kondo, lsp cache, and SCIP index files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 16:50:43 -05:00
9d1fd59644 update API 2026-02-03 13:01:03 -05:00
8f970ed5b4 fix layout issue 2026-02-03 12:51:04 -05:00
0702d27166 refactor 2026-02-03 12:22:19 -05:00
11 changed files with 776 additions and 435 deletions
+59
View File
@@ -0,0 +1,59 @@
name: SCIP Index
on:
push:
branches: ['**']
workflow_dispatch:
jobs:
index:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Clone clojure-tui (local dependency)
run: git clone https://git.ajet.fyi/ajet/clojure-tui.git ../clojure-tui
- name: Setup Java
run: |
sudo apt-get update
sudo apt-get install -y openjdk-17-jdk
echo "JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64" >> $GITHUB_ENV
- name: Install Clojure CLI
run: curl -fsSL https://download.clojure.org/install/linux-install-1.12.0.1501.sh | sudo bash
- name: Install Babashka
run: |
curl -fsSL https://raw.githubusercontent.com/babashka/babashka/master/install | sudo bash
- name: Install clojure-lsp
run: |
mkdir -p /usr/local/bin
curl -fsSL https://github.com/clojure-lsp/clojure-lsp/releases/latest/download/clojure-lsp-native-static-linux-amd64.zip -o /tmp/clojure-lsp.zip
unzip -o /tmp/clojure-lsp.zip -d /usr/local/bin/
chmod +x /usr/local/bin/clojure-lsp
- name: Clone and build scip-clojure
run: |
git clone https://git.ajet.fyi/ajet/scip-clojure.git /tmp/scip-clojure
cd /tmp/scip-clojure
clojure -T:build compile-java
- name: Generate SCIP index
run: |
cd /tmp/scip-clojure
clojure -M:run -p $GITHUB_WORKSPACE -o $GITHUB_WORKSPACE/index.scip -m $GITHUB_WORKSPACE/package-map.edn
env:
CLOJURE_LSP_PATH: /usr/local/bin/clojure-lsp
- name: Install Sourcegraph CLI
run: |
curl -fsSL https://sourcegraph.com/.api/src-cli/src_linux_amd64 -o /usr/local/bin/src
chmod +x /usr/local/bin/src
- name: Upload to Sourcegraph
run: src code-intel upload -file=index.scip
env:
SRC_ENDPOINT: ${{ secrets.SRC_ENDPOINT }}
SRC_ACCESS_TOKEN: ${{ secrets.SRC_ACCESS_TOKEN }}
+3
View File
@@ -2,6 +2,9 @@
.nrepl-port .nrepl-port
target/ target/
*.jar *.jar
.clj-kondo/
.lsp/
index.scip
# VHS test outputs (generated artifacts, not needed in repo) # VHS test outputs (generated artifacts, not needed in repo)
test/e2e/output/ test/e2e/output/
+16 -1
View File
@@ -8,10 +8,23 @@
## Local TUI Library ## Local TUI Library
- The TUI library is at `../clojure-tui/` (local dependency in bb.edn) - The TUI library is at `../clojure-tui/` — use local override for development:
```
bb -Sdeps '{:deps {io.github.ajet/clojure-tui {:local/root "../clojure-tui"}}}' start
```
- `bb.edn` uses a git dep (for bbin install), override with `:local/root` when developing against local TUI changes
- You have access to edit the local TUI library in conjunction with this repo - You have access to edit the local TUI library in conjunction with this repo
- Use this access to debug issues, support new features, and simplify code - Use this access to debug issues, support new features, and simplify code
- Look for opportunities to create better abstraction primitives for TUI layout - Look for opportunities to create better abstraction primitives for TUI layout
- When updating the TUI library, update the `:git/sha` in `bb.edn` after pushing clojure-tui changes
## Installation (bbin)
End users install from Gitea:
```
bbin install https://git.ajet.fyi/ajet/lazygitclj.git
```
This requires the `clojure-tui` git dep SHA in `bb.edn` to be up-to-date and pushed.
## Testing with VHS ## Testing with VHS
@@ -52,9 +65,11 @@ test/
| Command | Purpose | | Command | Purpose |
|---------|---------| |---------|---------|
| `bb start` | Run lazygitclj TUI | | `bb start` | Run lazygitclj TUI |
| `bb -Sdeps '{:deps {io.github.ajet/clojure-tui {:local/root "../clojure-tui"}}}' start` | Run with local TUI lib |
| `bb debug` | Debug TUI layout issues | | `bb debug` | Debug TUI layout issues |
| `bb test` | Run unit tests | | `bb test` | Run unit tests |
| `bb test:e2e` | Run VHS tape tests | | `bb test:e2e` | Run VHS tape tests |
| `bbin install https://git.ajet.fyi/ajet/lazygitclj.git` | Install for end users |
## Architecture: Elm Pattern ## Architecture: Elm Pattern
+2
View File
@@ -74,3 +74,5 @@ bb test:e2e # Run VHS tape tests
## License ## License
MIT MIT
+3 -1
View File
@@ -1,5 +1,7 @@
{:paths ["src" "test"] {:paths ["src" "test"]
:deps {io.github.ajet/clojure-tui {:local/root "../clojure-tui"}} :deps {io.github.ajet/clojure-tui {:git/url "https://git.ajet.fyi/ajet/clojure-tui.git"
:git/sha "4a051304888d5b4d937262decba919e1a79dd03d"}}
:bbin/bin {lazygitclj {:main-opts ["-m" "lazygitclj.core"]}}
:tasks {:requires ([e2e]) :tasks {:requires ([e2e])
start {:doc "Run lazygitclj" start {:doc "Run lazygitclj"
:task (exec 'lazygitclj.core/-main)} :task (exec 'lazygitclj.core/-main)}
+3
View File
@@ -0,0 +1,3 @@
{:paths ["src"]
:deps {io.github.ajet/clojure-tui {:git/url "https://git.ajet.fyi/ajet/clojure-tui.git"
:git/sha "4a051304888d5b4d937262decba919e1a79dd03d"}}}
View File
+9
View File
@@ -0,0 +1,9 @@
;; Package map for cross-repository SCIP navigation
;; Maps namespace prefixes to package@version coordinates
;;
;; This enables Sourcegraph to resolve cross-repo references:
;; - "tui" prefix -> clojure-tui repository
;; - "lazygitclj" prefix -> this repository
{"tui" "io.github.ajet/clojure-tui@main"
"lazygitclj" "io.github.ajet/lazygitclj@main"}
+501 -303
View File
File diff suppressed because it is too large Load Diff
+18
View File
@@ -436,3 +436,21 @@
"Checkout a tag (detached HEAD)." "Checkout a tag (detached HEAD)."
[name] [name]
(sh "git" "checkout" name)) (sh "git" "checkout" name))
;; === Patch Application ===
(defn apply-patch-cached
"Apply a patch to the index via stdin (stage a hunk)."
[patch-text]
(try
(shell {:in patch-text :out :string :err :string} "git" "apply" "--cached")
true
(catch Exception _ false)))
(defn unapply-patch-cached
"Reverse-apply a patch from the index via stdin (unstage a hunk)."
[patch-text]
(try
(shell {:in patch-text :out :string :err :string} "git" "apply" "--cached" "-R")
true
(catch Exception _ false)))
+96 -64
View File
@@ -1,15 +1,18 @@
(ns lazygitclj.core-test (ns lazygitclj.core-test
"Unit tests for lazygitclj.core namespace - model and update functions" "Unit tests for lazygitclj.core namespace - model and update functions"
(:require [clojure.test :refer [deftest testing is]] (:require [clojure.test :refer [deftest testing is]]
[lazygitclj.core :as core])) [lazygitclj.core :as core]
[tui.events :as ev]))
;; Helper to create key messages in the format the TUI library uses ;; Helper to create key events in the new format
(defn key-msg [k] (defn key-event
([k]
(cond (cond
(char? k) [:key {:char k}] (char? k) {:type :key :key k}
(keyword? k) [:key k] (keyword? k) {:type :key :key k}
(and (vector? k) (= :ctrl (first k))) [:key {:ctrl true :char (second k)}] :else {:type :key :key k}))
:else [:key k])) ([k modifiers]
{:type :key :key k :modifiers modifiers}))
;; === Model Tests === ;; === Model Tests ===
@@ -108,178 +111,207 @@
;; === Update Tests === ;; === Update Tests ===
(deftest test-update-model-quit (deftest test-update-model-quit
(testing "q returns quit command" (testing "q returns quit event"
(let [[model cmd] (core/update-model {} (key-msg \q))] (let [{:keys [events]} (core/update-model {:model {} :event (key-event \q)})]
(is (= [:quit] cmd)))) (is (= [{:type :quit}] events))))
(testing "ctrl-c returns quit command" (testing "ctrl-c returns quit event"
(let [[model cmd] (core/update-model {} [:key {:ctrl true :char \c}])] (let [{:keys [events]} (core/update-model {:model {} :event (key-event \c #{:ctrl})})]
(is (= [:quit] cmd))))) (is (= [{:type :quit}] events)))))
(deftest test-update-model-panel-switch (deftest test-update-model-panel-switch
(testing "number keys switch panels (2-5 matching lazygit)" (testing "number keys switch panels (2-5 matching lazygit)"
(let [[model _] (core/update-model {:panel :commits :cursor 0 (let [{:keys [model]} (core/update-model {:model {:panel :commits :cursor 0
:staged [] :unstaged []} (key-msg \2))] :staged [] :unstaged []}
:event (key-event \2)})]
(is (= :files (:panel model)))) (is (= :files (:panel model))))
(let [[model _] (core/update-model {:panel :files :cursor 0 (let [{:keys [model]} (core/update-model {:model {:panel :files :cursor 0
:branches-tab :local :branches-tab :local
:branches [] :remote-branches [] :branches [] :remote-branches []
:tags []} (key-msg \3))] :tags []}
:event (key-event \3)})]
(is (= :branches (:panel model)))) (is (= :branches (:panel model))))
(let [[model _] (core/update-model {:panel :files :cursor 0 (let [{:keys [model]} (core/update-model {:model {:panel :files :cursor 0
:commits-tab :commits :commits-tab :commits
:commits [] :reflog []} (key-msg \4))] :commits [] :reflog []}
:event (key-event \4)})]
(is (= :commits (:panel model)))) (is (= :commits (:panel model))))
(let [[model _] (core/update-model {:panel :files :cursor 0 (let [{:keys [model]} (core/update-model {:model {:panel :files :cursor 0
:stashes []} (key-msg \5))] :stashes []}
:event (key-event \5)})]
(is (= :stash (:panel model))))) (is (= :stash (:panel model)))))
(testing "l key cycles panels right (files → branches → commits → stash → files)" (testing "l key cycles panels right (files → branches → commits → stash → files)"
(let [[model _] (core/update-model {:panel :files :cursor 0 (let [{:keys [model]} (core/update-model {:model {:panel :files :cursor 0
:branches-tab :local :branches-tab :local
:branches [] :remote-branches [] :branches [] :remote-branches []
:tags []} (key-msg \l))] :tags []}
:event (key-event \l)})]
(is (= :branches (:panel model)))) (is (= :branches (:panel model))))
(let [[model _] (core/update-model {:panel :branches :cursor 0 (let [{:keys [model]} (core/update-model {:model {:panel :branches :cursor 0
:commits-tab :commits :commits-tab :commits
:commits [] :reflog []} (key-msg \l))] :commits [] :reflog []}
:event (key-event \l)})]
(is (= :commits (:panel model)))) (is (= :commits (:panel model))))
(let [[model _] (core/update-model {:panel :commits :cursor 0 (let [{:keys [model]} (core/update-model {:model {:panel :commits :cursor 0
:stashes []} (key-msg \l))] :stashes []}
:event (key-event \l)})]
(is (= :stash (:panel model)))) (is (= :stash (:panel model))))
(let [[model _] (core/update-model {:panel :stash :cursor 0 (let [{:keys [model]} (core/update-model {:model {:panel :stash :cursor 0
:staged [] :unstaged []} (key-msg \l))] :staged [] :unstaged []}
:event (key-event \l)})]
(is (= :files (:panel model)))))) (is (= :files (:panel model))))))
(deftest test-update-model-cursor-movement (deftest test-update-model-cursor-movement
(testing "j moves cursor down" (testing "j moves cursor down"
(let [[model _] (core/update-model {:panel :files :cursor 0 (let [{:keys [model]} (core/update-model {:model {:panel :files :cursor 0
:staged [{:path "a"} {:path "b"}] :staged [{:path "a"} {:path "b"}]
:unstaged []} (key-msg \j))] :unstaged []}
:event (key-event \j)})]
(is (= 1 (:cursor model))))) (is (= 1 (:cursor model)))))
(testing "k moves cursor up" (testing "k moves cursor up"
(let [[model _] (core/update-model {:panel :files :cursor 1 (let [{:keys [model]} (core/update-model {:model {:panel :files :cursor 1
:staged [{:path "a"} {:path "b"}] :staged [{:path "a"} {:path "b"}]
:unstaged []} (key-msg \k))] :unstaged []}
:event (key-event \k)})]
(is (= 0 (:cursor model))))) (is (= 0 (:cursor model)))))
(testing "down arrow moves cursor down" (testing "down arrow moves cursor down"
(let [[model _] (core/update-model {:panel :files :cursor 0 (let [{:keys [model]} (core/update-model {:model {:panel :files :cursor 0
:staged [{:path "a"} {:path "b"}] :staged [{:path "a"} {:path "b"}]
:unstaged []} (key-msg :down))] :unstaged []}
:event (key-event :down)})]
(is (= 1 (:cursor model))))) (is (= 1 (:cursor model)))))
(testing "up arrow moves cursor up" (testing "up arrow moves cursor up"
(let [[model _] (core/update-model {:panel :files :cursor 1 (let [{:keys [model]} (core/update-model {:model {:panel :files :cursor 1
:staged [{:path "a"} {:path "b"}] :staged [{:path "a"} {:path "b"}]
:unstaged []} (key-msg :up))] :unstaged []}
:event (key-event :up)})]
(is (= 0 (:cursor model)))))) (is (= 0 (:cursor model))))))
(deftest test-update-model-help-menu (deftest test-update-model-help-menu
(testing "? opens help menu" (testing "? opens help menu"
(let [[model _] (core/update-model {:menu-mode nil} (key-msg \?))] (let [{:keys [model]} (core/update-model {:model {:menu-mode nil}
:event (key-event \?)})]
(is (= :help (:menu-mode model)))))) (is (= :help (:menu-mode model))))))
(deftest test-update-model-reset-menu (deftest test-update-model-reset-menu
(testing "D opens reset options menu" (testing "D opens reset options menu"
(let [[model _] (core/update-model {:menu-mode nil} (key-msg \D))] (let [{:keys [model]} (core/update-model {:model {:menu-mode nil}
:event (key-event \d #{:shift})})]
(is (= :reset-options (:menu-mode model)))))) (is (= :reset-options (:menu-mode model))))))
(deftest test-update-model-input-mode (deftest test-update-model-input-mode
(testing "c in files panel opens commit input when staged files exist" (testing "c in files panel opens commit input when staged files exist"
(let [[model _] (core/update-model {:panel :files (let [{:keys [model]} (core/update-model {:model {:panel :files
:staged [{:path "a.txt"}] :staged [{:path "a.txt"}]
:unstaged []} (key-msg \c))] :unstaged []}
:event (key-event \c)})]
(is (= :commit (:input-mode model))))) (is (= :commit (:input-mode model)))))
(testing "c in files panel shows message when no staged files" (testing "c in files panel shows message when no staged files"
(let [[model _] (core/update-model {:panel :files (let [{:keys [model]} (core/update-model {:model {:panel :files
:staged [] :staged []
:unstaged []} (key-msg \c))] :unstaged []}
:event (key-event \c)})]
(is (= "Nothing staged to commit" (:message model)))))) (is (= "Nothing staged to commit" (:message model))))))
(deftest test-update-input-mode (deftest test-update-input-mode
(testing "escape cancels input mode" (testing "escape cancels input mode"
(let [[model _] (core/update-input-mode {:input-mode :commit (let [{:keys [model]} (core/update-input-mode {:model {:input-mode :commit
:input-buffer "test" :input-buffer "test"
:input-context nil} (key-msg :escape))] :input-context nil}
:event (key-event :escape)})]
(is (nil? (:input-mode model))) (is (nil? (:input-mode model)))
(is (= "" (:input-buffer model))))) (is (= "" (:input-buffer model)))))
(testing "backspace removes last character" (testing "backspace removes last character"
(let [[model _] (core/update-input-mode {:input-mode :commit (let [{:keys [model]} (core/update-input-mode {:model {:input-mode :commit
:input-buffer "abc" :input-buffer "abc"
:input-context nil} (key-msg :backspace))] :input-context nil}
:event (key-event :backspace)})]
(is (= "ab" (:input-buffer model)))))) (is (= "ab" (:input-buffer model))))))
(deftest test-update-commits-tabs (deftest test-update-commits-tabs
(testing "] switches to reflog tab in commits panel" (testing "] switches to reflog tab in commits panel"
(let [[model _] (core/update-model {:panel :commits (let [{:keys [model]} (core/update-model {:model {:panel :commits
:commits-tab :commits :commits-tab :commits
:cursor 0 :cursor 0
:commits [] :commits []
:reflog []} (key-msg \]))] :reflog []}
:event (key-event \])})]
(is (= :reflog (:commits-tab model))))) (is (= :reflog (:commits-tab model)))))
(testing "[ switches to commits tab in commits panel" (testing "[ switches to commits tab in commits panel"
(let [[model _] (core/update-model {:panel :commits (let [{:keys [model]} (core/update-model {:model {:panel :commits
:commits-tab :reflog :commits-tab :reflog
:cursor 0 :cursor 0
:commits [] :commits []
:reflog []} (key-msg \[))] :reflog []}
:event (key-event \[)})]
(is (= :commits (:commits-tab model)))))) (is (= :commits (:commits-tab model))))))
(deftest test-update-branches-tabs (deftest test-update-branches-tabs
(testing "] cycles branches tabs forward" (testing "] cycles branches tabs forward"
(let [[model _] (core/update-branches {:branches-tab :local (let [{:keys [model]} (core/update-branches {:model {:branches-tab :local
:cursor 0 :cursor 0
:branches []} (key-msg \]))] :branches []}
:event (key-event \])})]
(is (= :remotes (:branches-tab model)))) (is (= :remotes (:branches-tab model))))
(let [[model _] (core/update-branches {:branches-tab :remotes (let [{:keys [model]} (core/update-branches {:model {:branches-tab :remotes
:cursor 0 :cursor 0
:remote-branches []} (key-msg \]))] :remote-branches []}
:event (key-event \])})]
(is (= :tags (:branches-tab model)))) (is (= :tags (:branches-tab model))))
(let [[model _] (core/update-branches {:branches-tab :tags (let [{:keys [model]} (core/update-branches {:model {:branches-tab :tags
:cursor 0 :cursor 0
:tags []} (key-msg \]))] :tags []}
:event (key-event \])})]
(is (= :local (:branches-tab model))))) (is (= :local (:branches-tab model)))))
(testing "[ cycles branches tabs backward" (testing "[ cycles branches tabs backward"
(let [[model _] (core/update-branches {:branches-tab :local (let [{:keys [model]} (core/update-branches {:model {:branches-tab :local
:cursor 0 :cursor 0
:tags []} (key-msg \[))] :tags []}
:event (key-event \[)})]
(is (= :tags (:branches-tab model)))))) (is (= :tags (:branches-tab model))))))
(deftest test-update-stash-menu (deftest test-update-stash-menu
(testing "escape closes stash menu" (testing "escape closes stash menu"
(let [[model _] (core/update-stash-menu {:menu-mode :stash-options} (key-msg :escape))] (let [{:keys [model]} (core/update-stash-menu {:model {:menu-mode :stash-options}
:event (key-event :escape)})]
(is (nil? (:menu-mode model)))))) (is (nil? (:menu-mode model))))))
(deftest test-update-reset-menu (deftest test-update-reset-menu
(testing "escape closes reset menu" (testing "escape closes reset menu"
(let [[model _] (core/update-reset-menu {:menu-mode :reset-options} (key-msg :escape))] (let [{:keys [model]} (core/update-reset-menu {:model {:menu-mode :reset-options}
:event (key-event :escape)})]
(is (nil? (:menu-mode model)))))) (is (nil? (:menu-mode model))))))
(deftest test-update-help (deftest test-update-help
(testing "escape closes help" (testing "escape closes help"
(let [[model _] (core/update-help {:menu-mode :help} (key-msg :escape))] (let [{:keys [model]} (core/update-help {:model {:menu-mode :help}
:event (key-event :escape)})]
(is (nil? (:menu-mode model))))) (is (nil? (:menu-mode model)))))
(testing "q closes help" (testing "q closes help"
(let [[model _] (core/update-help {:menu-mode :help} (key-msg \q))] (let [{:keys [model]} (core/update-help {:model {:menu-mode :help}
:event (key-event \q)})]
(is (nil? (:menu-mode model))))) (is (nil? (:menu-mode model)))))
(testing "? closes help" (testing "? closes help"
(let [[model _] (core/update-help {:menu-mode :help} (key-msg \?))] (let [{:keys [model]} (core/update-help {:model {:menu-mode :help}
:event (key-event \?)})]
(is (nil? (:menu-mode model)))))) (is (nil? (:menu-mode model))))))
;; Run tests when executed directly ;; Run tests when executed directly