Cycle command-line history
Closes #43.
... | ... |
@@ -40,6 +40,44 @@ |
40 | 40 |
(fn [_ [_ x]] |
41 | 41 |
{:window/scroll-to x})) |
42 | 42 |
|
43 |
+(defn- cycle-history [cs direction] |
|
44 |
+ (let [history (-> cs :history) |
|
45 |
+ current (-> cs :current) |
|
46 |
+ orig (some-> cs :selected-history :orig) |
|
47 |
+ idx (some-> cs :selected-history :index) |
|
48 |
+ history-end (- (count history) 1) |
|
49 |
+ historical-input #(-> history (get %) :input) |
|
50 |
+ next (case direction :backward dec :forward inc) |
|
51 |
+ at-first? (= idx history-end) |
|
52 |
+ at-last? (= idx 0) |
|
53 |
+ going-forward? (= direction :forward) |
|
54 |
+ going-backward? (= direction :backward) |
|
55 |
+ not-cycling? (-> cs :selected-history nil?)] |
|
56 |
+ (cond |
|
57 |
+ (empty? history) {:current current :selected-history nil} |
|
58 |
+ (and at-first? going-forward?) {:current orig :selected-history nil} |
|
59 |
+ (and at-last? going-backward?) {} ; do nothing |
|
60 |
+ (and not-cycling? going-forward?) {} ; do nothing |
|
61 |
+ (and not-cycling? going-backward?) {:current (historical-input history-end) |
|
62 |
+ :selected-history {:orig current |
|
63 |
+ :index history-end}} |
|
64 |
+ :else {:current (historical-input (next idx)) |
|
65 |
+ :selected-history {:orig orig |
|
66 |
+ :index (next idx)}}))) |
|
67 |
+ |
|
68 |
+(re-frame/reg-event-db |
|
69 |
+ ::prompt-keypress |
|
70 |
+ (re-frame/path :cmdline) |
|
71 |
+ (fn [cmd [_ event]] |
|
72 |
+ (let [direction (case (.-key event) |
|
73 |
+ "ArrowUp" :backward |
|
74 |
+ "ArrowDown" :forward |
|
75 |
+ nil)] |
|
76 |
+ (if direction |
|
77 |
+ (do (.preventDefault event) |
|
78 |
+ (merge cmd (cycle-history cmd direction))) |
|
79 |
+ cmd)))) |
|
80 |
+ |
|
43 | 81 |
(re-frame/reg-event-fx |
44 | 82 |
::submit-cmd |
45 | 83 |
(fn [cofx [_ prompt]] |
... | ... |
@@ -51,9 +89,12 @@ |
51 | 89 |
; don't always have to prefix with a conditional |
52 | 90 |
(cond-> db |
53 | 91 |
true (update-in [:cmdline :current] (constantly nil)) |
92 |
+ true (update-in [:cmdline :selected-history] (constantly nil)) |
|
54 | 93 |
(not-empty? cmdline) |
55 | 94 |
(as-> db |
56 | 95 |
(let [output (execute db cmdline)] |
57 | 96 |
(update-in db [:cmdline :history] conj |
58 | 97 |
{:input cmdline :output output})))) |
59 |
- :fx [[:dispatch [::scroll-to prompt]]]}))) |
|
98 |
+ ; dispatch-later to ensure that the DOM has already been updated, |
|
99 |
+ ; so that scrollMaxY has been changed appropriately. |
|
100 |
+ :fx [[:dispatch-later {:ms 10 :dispatch [::scroll-to prompt]}]]}))) |
... | ... |
@@ -12,12 +12,14 @@ |
12 | 12 |
(let [cmdline (re-frame/subscribe [::subs/cmdline]) |
13 | 13 |
update-cmdline #(re-frame/dispatch-sync |
14 | 14 |
[::events/update-cmdline (-> % .-target .-value)]) |
15 |
+ key-pressed #(re-frame/dispatch-sync [::events/prompt-keypress %]) |
|
15 | 16 |
submit-cmd #(do (.preventDefault %) |
16 | 17 |
(re-frame/dispatch [::events/submit-cmd (-> % .-target)]))] |
17 | 18 |
[:form {:class (styles/prompt-style) |
18 | 19 |
:on-submit submit-cmd} |
19 | 20 |
[:input {:value @cmdline |
20 | 21 |
:on-change update-cmdline |
22 |
+ :on-keyDown key-pressed |
|
21 | 23 |
:type "text"}]])) |
22 | 24 |
|
23 | 25 |
(defn title [] |