|
|
|
@@ -6,6 +6,8 @@
|
|
|
|
|
[lazygitclj.git :as git]
|
|
|
|
|
[clojure.string :as str]))
|
|
|
|
|
|
|
|
|
|
(def temp tui/run)
|
|
|
|
|
|
|
|
|
|
;; === Model ===
|
|
|
|
|
|
|
|
|
|
(defn load-git-data []
|
|
|
|
@@ -55,47 +57,8 @@
|
|
|
|
|
(str (subs s 0 max-len) "...")
|
|
|
|
|
s))
|
|
|
|
|
|
|
|
|
|
(defn parse-diff-hunks
|
|
|
|
|
"Parse a diff string into header lines and hunk boundaries.
|
|
|
|
|
Returns {:header [lines] :hunks [{:start idx :end idx :lines [lines]} ...] :lines [all-lines]}"
|
|
|
|
|
[diff-text]
|
|
|
|
|
(when (and diff-text (not (str/blank? diff-text)))
|
|
|
|
|
(let [lines (vec (str/split-lines diff-text))
|
|
|
|
|
hunk-starts (vec (keep-indexed
|
|
|
|
|
(fn [i line] (when (str/starts-with? line "@@") i))
|
|
|
|
|
lines))]
|
|
|
|
|
(when (seq hunk-starts)
|
|
|
|
|
(let [header-end (first hunk-starts)
|
|
|
|
|
header (subvec lines 0 header-end)
|
|
|
|
|
hunks (mapv (fn [i]
|
|
|
|
|
(let [start (nth hunk-starts i)
|
|
|
|
|
end (if (< (inc i) (count hunk-starts))
|
|
|
|
|
(nth hunk-starts (inc i))
|
|
|
|
|
(count lines))]
|
|
|
|
|
{:start start :end end :lines (subvec lines start end)}))
|
|
|
|
|
(range (count hunk-starts)))]
|
|
|
|
|
{:header header :hunks hunks :lines lines})))))
|
|
|
|
|
|
|
|
|
|
(defn hunk-to-patch
|
|
|
|
|
"Construct a valid git patch from parsed diff header and a single hunk."
|
|
|
|
|
[parsed-diff hunk-idx]
|
|
|
|
|
(let [hunk (get (:hunks parsed-diff) hunk-idx)]
|
|
|
|
|
(when hunk
|
|
|
|
|
(str (str/join "\n" (:header parsed-diff)) "\n"
|
|
|
|
|
(str/join "\n" (:lines hunk)) "\n"))))
|
|
|
|
|
|
|
|
|
|
(defn line->hunk-index
|
|
|
|
|
"Find which hunk index contains the given line index, or nil if in header."
|
|
|
|
|
[parsed-diff line-idx]
|
|
|
|
|
(when parsed-diff
|
|
|
|
|
(first (keep-indexed
|
|
|
|
|
(fn [i hunk]
|
|
|
|
|
(when (and (>= line-idx (:start hunk)) (< line-idx (:end hunk)))
|
|
|
|
|
i))
|
|
|
|
|
(:hunks parsed-diff)))))
|
|
|
|
|
|
|
|
|
|
(defn get-current-diff
|
|
|
|
|
"Get the diff for the currently selected item based on panel and cursor."
|
|
|
|
|
"Change doc string for test"
|
|
|
|
|
[model]
|
|
|
|
|
(let [panel (:panel model)
|
|
|
|
|
items (current-items model)
|
|
|
|
@@ -129,7 +92,8 @@
|
|
|
|
|
;; === Update ===
|
|
|
|
|
|
|
|
|
|
(defn update-diff [model]
|
|
|
|
|
(assoc model :diff (get-current-diff model)))
|
|
|
|
|
;; never update lol
|
|
|
|
|
model)
|
|
|
|
|
|
|
|
|
|
(defn initial-model []
|
|
|
|
|
(let [base (merge
|
|
|
|
@@ -142,12 +106,6 @@
|
|
|
|
|
:menu-mode nil
|
|
|
|
|
:commits-tab :commits
|
|
|
|
|
:branches-tab :local
|
|
|
|
|
:diff-focused false
|
|
|
|
|
:diff-scroll 0
|
|
|
|
|
:diff-staging false
|
|
|
|
|
:diff-hunks nil
|
|
|
|
|
:diff-cursor 0
|
|
|
|
|
:diff-file-item nil
|
|
|
|
|
:reflog-index 0}
|
|
|
|
|
(load-git-data))]
|
|
|
|
|
(update-diff base)))
|
|
|
|
@@ -201,22 +159,6 @@
|
|
|
|
|
(key= event \s #{:shift})
|
|
|
|
|
{:model (assoc model :menu-mode :stash-options)}
|
|
|
|
|
|
|
|
|
|
;; Enter: interactive staging view
|
|
|
|
|
(key= event :enter)
|
|
|
|
|
(if item
|
|
|
|
|
(let [parsed (when (:diff model) (parse-diff-hunks (:diff model)))]
|
|
|
|
|
(if (and parsed (seq (:hunks parsed)))
|
|
|
|
|
{:model (assoc model
|
|
|
|
|
:diff-focused true
|
|
|
|
|
:diff-staging true
|
|
|
|
|
:diff-hunks parsed
|
|
|
|
|
:diff-cursor (:start (first (:hunks parsed)))
|
|
|
|
|
:diff-file-item item)}
|
|
|
|
|
(if (:diff model)
|
|
|
|
|
{:model (assoc model :diff-focused true :diff-scroll 0)}
|
|
|
|
|
{:model model})))
|
|
|
|
|
{:model model})
|
|
|
|
|
|
|
|
|
|
:else
|
|
|
|
|
{:model model})))
|
|
|
|
|
|
|
|
|
@@ -270,12 +212,6 @@
|
|
|
|
|
{:model (assoc model :message (str "SHA: " sha " (not copied - no clipboard support)"))}
|
|
|
|
|
{:model model})
|
|
|
|
|
|
|
|
|
|
;; Enter: focus diff view
|
|
|
|
|
(key= event :enter)
|
|
|
|
|
(if (some? (:diff model))
|
|
|
|
|
{:model (assoc model :diff-focused true :diff-scroll 0)}
|
|
|
|
|
{:model model})
|
|
|
|
|
|
|
|
|
|
:else
|
|
|
|
|
{:model model})))
|
|
|
|
|
|
|
|
|
@@ -298,9 +234,7 @@
|
|
|
|
|
(git/checkout-tag item)
|
|
|
|
|
{:model (-> model refresh (assoc :message (str "Checked out tag " item)))})
|
|
|
|
|
|
|
|
|
|
:else (if (some? (:diff model))
|
|
|
|
|
{:model (assoc model :diff-focused true :diff-scroll 0)}
|
|
|
|
|
{:model model}))
|
|
|
|
|
:else {:model model})
|
|
|
|
|
|
|
|
|
|
;; n: new branch (local tab only)
|
|
|
|
|
(and (key= event \n) (= tab :local))
|
|
|
|
@@ -378,12 +312,6 @@
|
|
|
|
|
(and (key= event \n) ref)
|
|
|
|
|
{:model (assoc model :input-mode :stash-branch :input-buffer "" :input-context ref)}
|
|
|
|
|
|
|
|
|
|
;; Enter: focus diff view
|
|
|
|
|
(key= event :enter)
|
|
|
|
|
(if (some? (:diff model))
|
|
|
|
|
{:model (assoc model :diff-focused true :diff-scroll 0)}
|
|
|
|
|
{:model model})
|
|
|
|
|
|
|
|
|
|
:else
|
|
|
|
|
{:model model})))
|
|
|
|
|
|
|
|
|
@@ -566,84 +494,6 @@
|
|
|
|
|
(:input-mode model)
|
|
|
|
|
(update-input-mode ctx)
|
|
|
|
|
|
|
|
|
|
;; Diff focused mode
|
|
|
|
|
(:diff-focused model)
|
|
|
|
|
(if (:diff-staging model)
|
|
|
|
|
;; Interactive staging mode: line-by-line navigation + hunk staging
|
|
|
|
|
(cond
|
|
|
|
|
(key= event :escape)
|
|
|
|
|
{:model (-> model
|
|
|
|
|
(assoc :diff-focused false :diff-staging false
|
|
|
|
|
:diff-hunks nil :diff-file-item nil)
|
|
|
|
|
refresh)}
|
|
|
|
|
|
|
|
|
|
(or (key= event \j) (key= event :down))
|
|
|
|
|
(let [max-idx (max 0 (dec (count (:lines (:diff-hunks model)))))]
|
|
|
|
|
{:model (update model :diff-cursor #(min max-idx (inc %)))})
|
|
|
|
|
|
|
|
|
|
(or (key= event \k) (key= event :up))
|
|
|
|
|
{:model (update model :diff-cursor #(max 0 (dec %)))}
|
|
|
|
|
|
|
|
|
|
;; Space: stage/unstage the hunk containing the current line
|
|
|
|
|
(key= event \space)
|
|
|
|
|
(let [{:keys [diff-hunks diff-cursor diff-file-item]} model
|
|
|
|
|
hunk-idx (line->hunk-index diff-hunks diff-cursor)
|
|
|
|
|
patch (when hunk-idx (hunk-to-patch diff-hunks hunk-idx))
|
|
|
|
|
staged? (= (:type diff-file-item) :staged)]
|
|
|
|
|
(if patch
|
|
|
|
|
(do
|
|
|
|
|
(if staged?
|
|
|
|
|
(git/unapply-patch-cached patch)
|
|
|
|
|
(git/apply-patch-cached patch))
|
|
|
|
|
(let [path (:path diff-file-item)
|
|
|
|
|
new-diff (if staged?
|
|
|
|
|
(git/diff-staged-file path)
|
|
|
|
|
(git/diff-unstaged path))
|
|
|
|
|
new-parsed (when new-diff (parse-diff-hunks new-diff))
|
|
|
|
|
new-model (-> model
|
|
|
|
|
(assoc :staged (git/staged-files)
|
|
|
|
|
:unstaged (git/unstaged-files))
|
|
|
|
|
clamp-cursor)
|
|
|
|
|
max-line (if new-parsed
|
|
|
|
|
(dec (count (:lines new-parsed)))
|
|
|
|
|
0)]
|
|
|
|
|
(if (and new-parsed (seq (:hunks new-parsed)))
|
|
|
|
|
{:model (assoc new-model
|
|
|
|
|
:diff new-diff
|
|
|
|
|
:diff-hunks new-parsed
|
|
|
|
|
:diff-cursor (min diff-cursor max-line))}
|
|
|
|
|
{:model (-> new-model
|
|
|
|
|
(assoc :diff-focused false :diff-staging false
|
|
|
|
|
:diff-hunks nil :diff-file-item nil)
|
|
|
|
|
update-diff
|
|
|
|
|
(assoc :message (if staged? "Unstaged hunk" "Staged hunk")))})))
|
|
|
|
|
{:model model}))
|
|
|
|
|
|
|
|
|
|
(or (key= event \q) (key= event \c #{:ctrl}))
|
|
|
|
|
{:model model :events [(ev/quit)]}
|
|
|
|
|
|
|
|
|
|
:else
|
|
|
|
|
{:model model})
|
|
|
|
|
|
|
|
|
|
;; Read-only scroll mode
|
|
|
|
|
(cond
|
|
|
|
|
(key= event :escape)
|
|
|
|
|
{:model (assoc model :diff-focused false)}
|
|
|
|
|
|
|
|
|
|
(or (key= event \j) (key= event :down))
|
|
|
|
|
(let [lines (when (:diff model) (str/split-lines (:diff model)))
|
|
|
|
|
max-scroll (max 0 (dec (count lines)))]
|
|
|
|
|
{:model (update model :diff-scroll #(min max-scroll (inc %)))})
|
|
|
|
|
|
|
|
|
|
(or (key= event \k) (key= event :up))
|
|
|
|
|
{:model (update model :diff-scroll #(max 0 (dec %)))}
|
|
|
|
|
|
|
|
|
|
(or (key= event \q) (key= event \c #{:ctrl}))
|
|
|
|
|
{:model model :events [(ev/quit)]}
|
|
|
|
|
|
|
|
|
|
:else
|
|
|
|
|
{:model model}))
|
|
|
|
|
|
|
|
|
|
(or (key= event \q) (key= event \c #{:ctrl}))
|
|
|
|
|
{:model model :events [(ev/quit)]}
|
|
|
|
|
|
|
|
|
@@ -660,29 +510,29 @@
|
|
|
|
|
|
|
|
|
|
;; Panel jump keys (matching lazygit: 2=Files, 3=Branches, 4=Commits, 5=Stash)
|
|
|
|
|
(key= event \2)
|
|
|
|
|
{:model (-> model (assoc :panel :files :cursor 0 :diff-focused false) clamp-cursor update-diff)}
|
|
|
|
|
{:model (-> model (assoc :panel :files :cursor 0) clamp-cursor update-diff)}
|
|
|
|
|
|
|
|
|
|
(key= event \3)
|
|
|
|
|
{:model (-> model (assoc :panel :branches :cursor 0 :diff-focused false) clamp-cursor update-diff)}
|
|
|
|
|
{:model (-> model (assoc :panel :branches :cursor 0) clamp-cursor update-diff)}
|
|
|
|
|
|
|
|
|
|
(key= event \4)
|
|
|
|
|
{:model (-> model (assoc :panel :commits :cursor 0 :diff-focused false) clamp-cursor update-diff)}
|
|
|
|
|
{:model (-> model (assoc :panel :commits :cursor 0) clamp-cursor update-diff)}
|
|
|
|
|
|
|
|
|
|
(key= event \5)
|
|
|
|
|
{:model (-> model (assoc :panel :stash :cursor 0 :diff-focused false) clamp-cursor update-diff)}
|
|
|
|
|
{:model (-> model (assoc :panel :stash :cursor 0) clamp-cursor update-diff)}
|
|
|
|
|
|
|
|
|
|
;; h/l or Left/Right: move between panels (order: files → branches → commits → stash)
|
|
|
|
|
(or (key= event \h) (key= event :left))
|
|
|
|
|
{:model (-> model
|
|
|
|
|
(update :panel #(case % :files :stash :branches :files :commits :branches :stash :commits))
|
|
|
|
|
(assoc :cursor 0 :diff-focused false)
|
|
|
|
|
(assoc :cursor 0)
|
|
|
|
|
clamp-cursor
|
|
|
|
|
update-diff)}
|
|
|
|
|
|
|
|
|
|
(or (key= event \l) (key= event :right))
|
|
|
|
|
{:model (-> model
|
|
|
|
|
(update :panel #(case % :files :branches :branches :commits :commits :stash :stash :files))
|
|
|
|
|
(assoc :cursor 0 :diff-focused false)
|
|
|
|
|
(assoc :cursor 0)
|
|
|
|
|
clamp-cursor
|
|
|
|
|
update-diff)}
|
|
|
|
|
|
|
|
|
@@ -748,46 +598,6 @@
|
|
|
|
|
(assoc :message (str "Redo: reset to " (:ref entry))))})
|
|
|
|
|
{:model (assoc model :message "Nothing to redo")}))
|
|
|
|
|
|
|
|
|
|
;; Mouse: click tab label to switch tab
|
|
|
|
|
(= (:type event) :switch-tab)
|
|
|
|
|
(let [{:keys [panel tab]} event
|
|
|
|
|
tab-key (case panel
|
|
|
|
|
:branches :branches-tab
|
|
|
|
|
:commits :commits-tab
|
|
|
|
|
nil)]
|
|
|
|
|
(if tab-key
|
|
|
|
|
{:model (-> model
|
|
|
|
|
(assoc :panel panel tab-key tab :cursor 0)
|
|
|
|
|
clamp-cursor
|
|
|
|
|
update-diff)}
|
|
|
|
|
{:model model}))
|
|
|
|
|
|
|
|
|
|
;; Mouse: click to switch panel
|
|
|
|
|
(= (:type event) :switch-panel)
|
|
|
|
|
{:model (-> model
|
|
|
|
|
(assoc :panel (:panel event) :cursor 0)
|
|
|
|
|
clamp-cursor
|
|
|
|
|
update-diff)}
|
|
|
|
|
|
|
|
|
|
;; Mouse: click to select item
|
|
|
|
|
(= (:type event) :select-item)
|
|
|
|
|
{:model (-> model
|
|
|
|
|
(assoc :panel (:panel event) :cursor (:index event))
|
|
|
|
|
clamp-cursor
|
|
|
|
|
update-diff)}
|
|
|
|
|
|
|
|
|
|
;; Mouse: click to select diff line in staging view
|
|
|
|
|
(= (:type event) :select-diff-line)
|
|
|
|
|
{:model (assoc model :diff-cursor (:index event))}
|
|
|
|
|
|
|
|
|
|
;; Mouse: scroll wheel in panel
|
|
|
|
|
(= (:type event) :scroll-panel)
|
|
|
|
|
{:model (-> model
|
|
|
|
|
(assoc :panel (:panel event))
|
|
|
|
|
(update :cursor (if (= (:direction event) :wheel-up) dec inc))
|
|
|
|
|
clamp-cursor
|
|
|
|
|
update-diff)}
|
|
|
|
|
|
|
|
|
|
:else
|
|
|
|
|
(case (:panel model)
|
|
|
|
|
:files (update-files ctx)
|
|
|
|
@@ -816,135 +626,94 @@
|
|
|
|
|
(when sync-info [:text {:fg :cyan} sync-info])]))]))
|
|
|
|
|
|
|
|
|
|
;; Panel 2: Files
|
|
|
|
|
(defn files-panel [{:keys [staged unstaged cursor panel diff-focused]}]
|
|
|
|
|
(defn files-panel [{:keys [staged unstaged cursor panel]}]
|
|
|
|
|
(let [active? (= panel :files)
|
|
|
|
|
focused? (and active? (not diff-focused))
|
|
|
|
|
all-files (into (mapv #(assoc % :section :staged) staged)
|
|
|
|
|
(mapv #(assoc % :section :unstaged) unstaged))
|
|
|
|
|
total (count all-files)]
|
|
|
|
|
[:box {:border (if focused? :double :single)
|
|
|
|
|
[:box {:border (if active? :double :single)
|
|
|
|
|
:title (str "2 Files (" total ")")
|
|
|
|
|
:on-click {:type :switch-panel :panel :files}
|
|
|
|
|
:on-scroll {:type :scroll-panel :panel :files}
|
|
|
|
|
:padding [0 1] :width :fill :height :fill}
|
|
|
|
|
(if (empty? all-files)
|
|
|
|
|
[:text {:fg :gray} "No changes"]
|
|
|
|
|
(into [:scroll {:cursor (if active? cursor 0)
|
|
|
|
|
:on-scroll {:type :scroll-panel :panel :files}}]
|
|
|
|
|
(into [:scroll {:cursor (if active? cursor 0)}]
|
|
|
|
|
(for [[idx file] (map-indexed vector all-files)]
|
|
|
|
|
(let [is-cursor (and active? (= idx cursor))
|
|
|
|
|
color (if (= (:section file) :staged) :green :red)]
|
|
|
|
|
[:row {:gap 1 :on-click {:type :select-item :panel :files :index idx}}
|
|
|
|
|
[:row {:gap 1}
|
|
|
|
|
[:text {:fg color} (file-status-char file)]
|
|
|
|
|
[:text {:fg (if is-cursor :cyan color) :bold is-cursor :inverse is-cursor}
|
|
|
|
|
(truncate (:path file) 20)]]))))]))
|
|
|
|
|
|
|
|
|
|
;; Panel 3: Branches
|
|
|
|
|
(defn- tab-label [text tab-key current-tab]
|
|
|
|
|
(if (= tab-key current-tab) (str "[" text "]") text))
|
|
|
|
|
|
|
|
|
|
(defn branches-panel [{:keys [branches remote-branches tags branch branches-tab cursor panel diff-focused]}]
|
|
|
|
|
(defn branches-panel [{:keys [branches remote-branches tags branch branches-tab cursor panel]}]
|
|
|
|
|
(let [active? (= panel :branches)
|
|
|
|
|
focused? (and active? (not diff-focused))
|
|
|
|
|
all-items (case branches-tab :local branches :remotes remote-branches :tags tags branches)]
|
|
|
|
|
[:box {:border (if focused? :double :single)
|
|
|
|
|
:title (str "3 Branches "
|
|
|
|
|
(tab-label "L" :local branches-tab) " "
|
|
|
|
|
(tab-label "R" :remotes branches-tab) " "
|
|
|
|
|
(tab-label "T" :tags branches-tab))
|
|
|
|
|
:on-click {:type :switch-panel :panel :branches}
|
|
|
|
|
:on-scroll {:type :scroll-panel :panel :branches}
|
|
|
|
|
all-items (case branches-tab :local branches :remotes remote-branches :tags tags branches)
|
|
|
|
|
tab-str (case branches-tab :local "[L] R T" :remotes "L [R] T" :tags "L R [T]")]
|
|
|
|
|
[:box {:border (if active? :double :single)
|
|
|
|
|
:title (str "3 Branches " tab-str)
|
|
|
|
|
:padding [0 1] :width :fill :height :fill}
|
|
|
|
|
(if (empty? all-items)
|
|
|
|
|
[:text {:fg :gray} (str "No " (name branches-tab))]
|
|
|
|
|
(into [:scroll {:cursor (if active? cursor 0)
|
|
|
|
|
:on-scroll {:type :scroll-panel :panel :branches}}]
|
|
|
|
|
(into [:scroll {:cursor (if active? cursor 0)}]
|
|
|
|
|
(for [[idx item] (map-indexed vector all-items)]
|
|
|
|
|
(let [is-cursor (and active? (= idx cursor))
|
|
|
|
|
is-current (and (= branches-tab :local) (= item branch))
|
|
|
|
|
fg (cond is-current :green is-cursor :cyan :else :white)]
|
|
|
|
|
[:text {:fg fg :bold (or is-current is-cursor) :inverse is-cursor
|
|
|
|
|
:on-click {:type :select-item :panel :branches :index idx}}
|
|
|
|
|
[:text {:fg fg :bold (or is-current is-cursor) :inverse is-cursor}
|
|
|
|
|
(str (if is-current "* " " ") (truncate item 22))]))))]))
|
|
|
|
|
|
|
|
|
|
;; Panel 4: Commits
|
|
|
|
|
(defn commits-panel [{:keys [commits reflog commits-tab cursor panel diff-focused]}]
|
|
|
|
|
(defn commits-panel [{:keys [commits reflog commits-tab cursor panel]}]
|
|
|
|
|
(let [active? (= panel :commits)
|
|
|
|
|
focused? (and active? (not diff-focused))
|
|
|
|
|
reflog? (= commits-tab :reflog)
|
|
|
|
|
all-items (if reflog? reflog commits)]
|
|
|
|
|
[:box {:border (if focused? :double :single)
|
|
|
|
|
:title (str "4 Commits "
|
|
|
|
|
(tab-label "C" :commits commits-tab) " "
|
|
|
|
|
(tab-label "R" :reflog commits-tab))
|
|
|
|
|
:on-click {:type :switch-panel :panel :commits}
|
|
|
|
|
:on-scroll {:type :scroll-panel :panel :commits}
|
|
|
|
|
[:box {:border (if active? :double :single)
|
|
|
|
|
:title (str "4 Commits " (if reflog? "C [R]" "[C] R"))
|
|
|
|
|
:padding [0 1] :width :fill :height :fill}
|
|
|
|
|
(if (empty? all-items)
|
|
|
|
|
[:text {:fg :gray} "No commits"]
|
|
|
|
|
(into [:scroll {:cursor (if active? cursor 0)
|
|
|
|
|
:on-scroll {:type :scroll-panel :panel :commits}}]
|
|
|
|
|
(into [:scroll {:cursor (if active? cursor 0)}]
|
|
|
|
|
(for [[idx {:keys [sha action subject]}] (map-indexed vector all-items)]
|
|
|
|
|
(let [is-cursor (and active? (= idx cursor))]
|
|
|
|
|
[:row {:gap 1 :on-click {:type :select-item :panel :commits :index idx}}
|
|
|
|
|
[:row {:gap 1}
|
|
|
|
|
[:text {:fg :yellow} (or sha "?")]
|
|
|
|
|
[:text {:fg (if is-cursor :cyan :white) :bold is-cursor :inverse is-cursor}
|
|
|
|
|
(truncate (if reflog? (str action ": " subject) (or subject "")) 14)]]))))]))
|
|
|
|
|
|
|
|
|
|
;; Panel 5: Stash
|
|
|
|
|
(defn stash-panel [{:keys [stashes cursor panel diff-focused]}]
|
|
|
|
|
(defn stash-panel [{:keys [stashes cursor panel]}]
|
|
|
|
|
(let [active? (= panel :stash)
|
|
|
|
|
focused? (and active? (not diff-focused))
|
|
|
|
|
total (count stashes)]
|
|
|
|
|
[:box {:border (if focused? :double :single)
|
|
|
|
|
[:box {:border (if active? :double :single)
|
|
|
|
|
:title (str "5 Stash (" total ")")
|
|
|
|
|
:on-click {:type :switch-panel :panel :stash}
|
|
|
|
|
:on-scroll {:type :scroll-panel :panel :stash}
|
|
|
|
|
:padding [0 1] :width :fill :height :fill}
|
|
|
|
|
(if (empty? stashes)
|
|
|
|
|
[:text {:fg :gray} "No stashes"]
|
|
|
|
|
(into [:scroll {:cursor (if active? cursor 0)
|
|
|
|
|
:on-scroll {:type :scroll-panel :panel :stash}}]
|
|
|
|
|
(into [:scroll {:cursor (if active? cursor 0)}]
|
|
|
|
|
(for [[idx {:keys [index message]}] (map-indexed vector stashes)]
|
|
|
|
|
(let [is-cursor (and active? (= idx cursor))]
|
|
|
|
|
[:row {:gap 1 :on-click {:type :select-item :panel :stash :index idx}}
|
|
|
|
|
[:row {:gap 1}
|
|
|
|
|
[:text {:fg :yellow} (str (or index idx))]
|
|
|
|
|
[:text {:fg (if is-cursor :cyan :white) :bold is-cursor :inverse is-cursor}
|
|
|
|
|
(truncate (or message "") 20)]]))))]))
|
|
|
|
|
|
|
|
|
|
(defn diff-line-color [line]
|
|
|
|
|
(cond
|
|
|
|
|
(str/starts-with? line "+") :green
|
|
|
|
|
(str/starts-with? line "-") :red
|
|
|
|
|
(str/starts-with? line "@@") :cyan
|
|
|
|
|
(or (str/starts-with? line "diff")
|
|
|
|
|
(str/starts-with? line "commit")) :yellow
|
|
|
|
|
:else :white))
|
|
|
|
|
|
|
|
|
|
;; Main View (Diff)
|
|
|
|
|
(defn main-view-panel [{:keys [diff diff-focused diff-scroll
|
|
|
|
|
diff-staging diff-hunks diff-cursor
|
|
|
|
|
diff-file-item]}]
|
|
|
|
|
(let [lines (when diff (str/split-lines diff))
|
|
|
|
|
title (if (and diff-staging diff-file-item)
|
|
|
|
|
(str "Staging - " (:path diff-file-item))
|
|
|
|
|
"Main")]
|
|
|
|
|
[:box {:border (if diff-focused :double :single) :title title
|
|
|
|
|
:padding [0 1] :width :fill :height :fill}
|
|
|
|
|
;; Panel 0: Main View (Diff)
|
|
|
|
|
(defn main-view-panel [{:keys [diff]}]
|
|
|
|
|
(let [lines (when diff (str/split-lines diff))]
|
|
|
|
|
[:box {:border :single :title "0 Main" :padding [0 1] :width :fill :height :fill}
|
|
|
|
|
(if (empty? lines)
|
|
|
|
|
[:text {:fg :gray} "Select an item to view diff"]
|
|
|
|
|
(if diff-staging
|
|
|
|
|
;; Interactive staging: highlight cursor line, click selects line
|
|
|
|
|
(into [:scroll {:cursor (or diff-cursor 0)}]
|
|
|
|
|
(for [[idx line] (map-indexed vector lines)]
|
|
|
|
|
[:text {:fg (diff-line-color line)
|
|
|
|
|
:inverse (= idx diff-cursor)
|
|
|
|
|
:on-click {:type :select-diff-line :index idx}}
|
|
|
|
|
line]))
|
|
|
|
|
;; Read-only scroll
|
|
|
|
|
(into [:scroll {:cursor (if diff-focused (or diff-scroll 0) 0)}]
|
|
|
|
|
(for [line lines]
|
|
|
|
|
[:text {:fg (diff-line-color line)} line]))))]))
|
|
|
|
|
[:col
|
|
|
|
|
(for [line lines]
|
|
|
|
|
[:text {:fg (cond
|
|
|
|
|
(str/starts-with? line "+") :green
|
|
|
|
|
(str/starts-with? line "-") :red
|
|
|
|
|
(str/starts-with? line "@@") :cyan
|
|
|
|
|
(or (str/starts-with? line "diff")
|
|
|
|
|
(str/starts-with? line "commit")) :yellow
|
|
|
|
|
:else :white)}
|
|
|
|
|
line])])]))
|
|
|
|
|
|
|
|
|
|
;; Command Log (placeholder)
|
|
|
|
|
(defn command-log-panel []
|
|
|
|
@@ -953,27 +722,22 @@
|
|
|
|
|
|
|
|
|
|
;; Bottom help bar
|
|
|
|
|
(defn help-bar [model]
|
|
|
|
|
(if (:diff-staging model)
|
|
|
|
|
[:row {:gap 1}
|
|
|
|
|
[:text {:fg :gray} "esc:back"]
|
|
|
|
|
[:text {:fg :gray} "j/k:hunks"]
|
|
|
|
|
[:text {:fg :gray} "spc:stage/unstage hunk"]]
|
|
|
|
|
(let [panel (:panel model)
|
|
|
|
|
panel-help (case panel
|
|
|
|
|
:files "spc:stage a:all c:commit enter:staging"
|
|
|
|
|
:commits "[]:tabs spc:checkout enter:view"
|
|
|
|
|
:branches "[]:tabs n:new d:del"
|
|
|
|
|
:stash "spc:apply g:pop d:drop enter:view"
|
|
|
|
|
"")]
|
|
|
|
|
(into [:row {:gap 1}]
|
|
|
|
|
(remove nil?
|
|
|
|
|
[[:text {:fg :gray} "q:quit"]
|
|
|
|
|
[:text {:fg :gray} "?:help"]
|
|
|
|
|
[:text {:fg :gray} "h/l:panels"]
|
|
|
|
|
[:text {:fg :gray} "j/k:nav"]
|
|
|
|
|
(when (seq panel-help)
|
|
|
|
|
[:text {:fg :gray} panel-help])
|
|
|
|
|
[:text {:fg :gray} "p/P:pull/push"]])))))
|
|
|
|
|
(let [panel (:panel model)
|
|
|
|
|
panel-help (case panel
|
|
|
|
|
:files "spc:stage a:all c:commit"
|
|
|
|
|
:commits "[]:tabs spc:checkout"
|
|
|
|
|
:branches "[]:tabs n:new d:del"
|
|
|
|
|
:stash "spc:apply g:pop d:drop"
|
|
|
|
|
"")]
|
|
|
|
|
(into [:row {:gap 1}]
|
|
|
|
|
(remove nil?
|
|
|
|
|
[[:text {:fg :gray} "q:quit"]
|
|
|
|
|
[:text {:fg :gray} "?:help"]
|
|
|
|
|
[:text {:fg :gray} "h/l:panels"]
|
|
|
|
|
[:text {:fg :gray} "j/k:nav"]
|
|
|
|
|
(when (seq panel-help)
|
|
|
|
|
[:text {:fg :gray} panel-help])
|
|
|
|
|
[:text {:fg :gray} "p/P:pull/push"]]))))
|
|
|
|
|
|
|
|
|
|
(defn stash-menu-view [{:keys [menu-mode]}]
|
|
|
|
|
(when (= menu-mode :stash-options)
|
|
|
|
@@ -1008,8 +772,7 @@
|
|
|
|
|
[:text {:fg :cyan :bold true} "Global:"]
|
|
|
|
|
[:text " q - Quit r - Refresh"]
|
|
|
|
|
[:text " h/l - Prev/Next panel 2-5 - Jump to panel"]
|
|
|
|
|
[:text " j/k - Move down/up enter - Focus diff"]
|
|
|
|
|
[:text " z/Z - Undo/Redo esc - Return from diff"]
|
|
|
|
|
[:text " j/k - Move down/up z/Z - Undo/Redo"]
|
|
|
|
|
[:text " p - Pull P - Push"]
|
|
|
|
|
[:text " ? - Help D - Reset options"]
|
|
|
|
|
[:text ""]
|
|
|
|
@@ -1119,16 +882,20 @@
|
|
|
|
|
:else
|
|
|
|
|
background)))
|
|
|
|
|
|
|
|
|
|
(defn test-view-2 [& args]
|
|
|
|
|
[:box "foo"])
|
|
|
|
|
|
|
|
|
|
;; === Main ===
|
|
|
|
|
|
|
|
|
|
(def temp2 get-current-diff)
|
|
|
|
|
|
|
|
|
|
(defn -main [& _args]
|
|
|
|
|
(if (git/repo-root)
|
|
|
|
|
(do
|
|
|
|
|
(println "Starting lazygitclj...")
|
|
|
|
|
(println (str "String with initial model: " (initial-model)))
|
|
|
|
|
(tui/run {:init (initial-model)
|
|
|
|
|
:update update-model
|
|
|
|
|
:view view
|
|
|
|
|
:mouse true})
|
|
|
|
|
:view test-view-2})
|
|
|
|
|
(println "Goodbye!"))
|
|
|
|
|
(do
|
|
|
|
|
(println "Error: Not a git repository")
|
|
|
|
|