Compare commits
No commits in common. "9775e0054590486a740b33d1ae801c4154caca9c" and "ad2628ddfc16d4868d8d837d71d92e604daf8ad4" have entirely different histories.
9775e00545
...
ad2628ddfc
@ -32,7 +32,6 @@
|
|||||||
(set property-change-listener cb))
|
(set property-change-listener cb))
|
||||||
:location #(widget:get_uri)
|
:location #(widget:get_uri)
|
||||||
:visit (fn [_ u] (widget:load_uri u))
|
:visit (fn [_ u] (widget:load_uri u))
|
||||||
:back #(widget:go_back)
|
|
||||||
:properties props}))
|
:properties props}))
|
||||||
|
|
||||||
(let [buffers {}]
|
(let [buffers {}]
|
||||||
|
54
command.fnl
54
command.fnl
@ -24,22 +24,23 @@
|
|||||||
|
|
||||||
(define-command
|
(define-command
|
||||||
"switch-to-buffer"
|
"switch-to-buffer"
|
||||||
[[:buffer Buffer.match #(. (Buffer.next $1.buffer) :name)]]
|
[[:buffer
|
||||||
(fn [{: frame : buffer}]
|
Buffer.match
|
||||||
|
#(. (Buffer.next $1.buffer) :name)]
|
||||||
|
]
|
||||||
|
(fn [{:frame frame :buffer buffer}]
|
||||||
(frame:show-buffer buffer)))
|
(frame:show-buffer buffer)))
|
||||||
|
|
||||||
(define-command
|
(define-command
|
||||||
"visit-location"
|
"visit-location"
|
||||||
[[:buffer Buffer.match #$1.buffer.name]
|
[[:buffer
|
||||||
[:url #{$1 $1} #($1.buffer:location)]]
|
Buffer.match
|
||||||
(fn [{: url : buffer}]
|
#$1.buffer.name]
|
||||||
|
[:url #{$1 $1} #($1.buffer:location)]
|
||||||
|
]
|
||||||
|
(fn [{:url url :buffer buffer}]
|
||||||
(buffer:visit url)))
|
(buffer:visit url)))
|
||||||
|
|
||||||
(define-command
|
|
||||||
"back"
|
|
||||||
[[:buffer Buffer.match #$1.buffer.name]]
|
|
||||||
(fn [{: buffer}] (buffer:back)))
|
|
||||||
|
|
||||||
(fn find-command [name]
|
(fn find-command [name]
|
||||||
(. commands name))
|
(. commands name))
|
||||||
|
|
||||||
@ -56,6 +57,9 @@
|
|||||||
&until v]
|
&until v]
|
||||||
(if (. params k) nil k)))
|
(if (. params k) nil k)))
|
||||||
|
|
||||||
|
(fn invoke-command [command params]
|
||||||
|
(command.function params))
|
||||||
|
|
||||||
(fn next-action [self input-string]
|
(fn next-action [self input-string]
|
||||||
(let [state self.state
|
(let [state self.state
|
||||||
state-for-next-param
|
state-for-next-param
|
||||||
@ -68,7 +72,7 @@
|
|||||||
:active true
|
:active true
|
||||||
}
|
}
|
||||||
_ (let [params (lume.extend {} {:frame self.frame} params)]
|
_ (let [params (lume.extend {} {:frame self.frame} params)]
|
||||||
(c.function params)
|
(invoke-command c params)
|
||||||
{:active false})))]
|
{:active false})))]
|
||||||
|
|
||||||
(match state
|
(match state
|
||||||
@ -87,12 +91,14 @@
|
|||||||
|
|
||||||
{:command c :this-param k :collected-params p}
|
{:command c :this-param k :collected-params p}
|
||||||
(let [{ : completer} (. c.params k)
|
(let [{ : completer} (. c.params k)
|
||||||
value (. (completer input-string) input-string)]
|
vals (completer input-string)
|
||||||
|
value (. vals input-string)]
|
||||||
(tset p k value)
|
(tset p k value)
|
||||||
(state-for-next-param c p))
|
(state-for-next-param c p))
|
||||||
|
|
||||||
{:command c :this-param nil :collected-params p}
|
{:command c :this-param nil :collected-params p}
|
||||||
(state-for-next-param c p)
|
(do
|
||||||
|
(state-for-next-param c p))
|
||||||
|
|
||||||
_ (do (print "unexpected state " (view state))
|
_ (do (print "unexpected state " (view state))
|
||||||
state)
|
state)
|
||||||
@ -103,20 +109,18 @@
|
|||||||
param (if s.active (. (. s.command :params) s.this-param))]
|
param (if s.active (. (. s.command :params) s.this-param))]
|
||||||
(set self.state s)
|
(set self.state s)
|
||||||
{
|
{
|
||||||
|
:active s.active
|
||||||
:error s.error
|
:error s.error
|
||||||
:prompt (if s.active (or s.this-param "Command?" "") "")
|
:prompt (if s.active (or s.this-param "Command?" "") "")
|
||||||
:default (and param (param.default self.frame))
|
:default (and param (param.default self.frame))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
(fn update-widget-state [{ : state : widget : entry : completions-widget : prompt} result]
|
(fn update-widget-state [{ : entry : completions-widget : prompt} result]
|
||||||
(set prompt.label (or result.prompt ""))
|
(set prompt.label (or result.prompt ""))
|
||||||
(set entry.sensitive state.active)
|
(set entry.sensitive result.active)
|
||||||
(if (not state.active)
|
(if (not result.active)
|
||||||
(completions-widget:hide))
|
(completions-widget:hide))
|
||||||
(set entry.text (or result.default result.error ""))
|
(set entry.text (or result.default result.error "")))
|
||||||
(match widget.parent
|
|
||||||
p (p:set_visible_child_name
|
|
||||||
(if state.active "commander" "echo-area"))))
|
|
||||||
|
|
||||||
(fn on-input [self str]
|
(fn on-input [self str]
|
||||||
(match self.state
|
(match self.state
|
||||||
@ -139,16 +143,17 @@
|
|||||||
(tset state :active true)
|
(tset state :active true)
|
||||||
(update-widget-state
|
(update-widget-state
|
||||||
self
|
self
|
||||||
{
|
{:active true
|
||||||
:prompt (or state.this-param "Command" "")
|
:prompt (or state.this-param "Command" "")
|
||||||
})
|
})
|
||||||
(entry:grab_focus))
|
(entry:grab_focus)
|
||||||
|
state)
|
||||||
|
|
||||||
(fn deactivate [{: state : entry : prompt &as self}]
|
(fn deactivate [{: state : entry : prompt &as self}]
|
||||||
(doto state
|
(doto state
|
||||||
(lume.clear)
|
(lume.clear)
|
||||||
(tset :active false))
|
(tset :active false))
|
||||||
(update-widget-state self {}))
|
(update-widget-state self {:active false}))
|
||||||
|
|
||||||
(fn invoke-interactively [self name params]
|
(fn invoke-interactively [self name params]
|
||||||
(let [c (find-command name)
|
(let [c (find-command name)
|
||||||
@ -187,6 +192,9 @@
|
|||||||
:widget box
|
:widget box
|
||||||
: prompt
|
: prompt
|
||||||
: frame
|
: frame
|
||||||
|
:set-inactive-text (fn [self text]
|
||||||
|
(when (not self.state.active)
|
||||||
|
(tset entry :text text)))
|
||||||
:completions-widget completions
|
:completions-widget completions
|
||||||
}]
|
}]
|
||||||
(hbox:pack_start prompt false false 15)
|
(hbox:pack_start prompt false false 15)
|
||||||
|
@ -99,12 +99,6 @@ To see how commands are implemented, read the code in `command.fnl`.
|
|||||||
There is a simple keymap in `dunlin.fnl`, and you can see the details
|
There is a simple keymap in `dunlin.fnl`, and you can see the details
|
||||||
of how keymaps work in `keymap.fnl`
|
of how keymaps work in `keymap.fnl`
|
||||||
|
|
||||||
When writing key bindings or printing errors, Dunlin assumes that the
|
|
||||||
key producing "Mod 1" (often labelled Alt) is the Meta key, and the
|
|
||||||
key producing "Mod 4" (on a PC, typically the key with the Windows logo)
|
|
||||||
is the Super key. For me this matches how Emacs does it, but I would
|
|
||||||
welcome reports of machines/setups that don't act ths way
|
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
(local my-keymap {
|
(local my-keymap {
|
||||||
"g" ["visit-location" {:buffer #$1.buffer }]
|
"g" ["visit-location" {:buffer #$1.buffer }]
|
||||||
"BackSpace" ["back" {:buffer #$1.buffer }]
|
|
||||||
"M-q" ["quit-browser" {}]
|
"M-q" ["quit-browser" {}]
|
||||||
"C-x" {
|
"C-x" {
|
||||||
"C-c" ["quit-browser" {}]
|
"C-c" ["quit-browser" {}]
|
||||||
|
31
frame.fnl
31
frame.fnl
@ -12,15 +12,7 @@
|
|||||||
vpad 2
|
vpad 2
|
||||||
self {}
|
self {}
|
||||||
recogniser (keymap.recogniser global-keymap)
|
recogniser (keymap.recogniser global-keymap)
|
||||||
bottom-line (Gtk.Stack {
|
|
||||||
:transition_type Gtk.StackTransitionType.SLIDE_UP_DOWN
|
|
||||||
:transition_duration 100
|
|
||||||
})
|
|
||||||
commander (Command.commander self)
|
commander (Command.commander self)
|
||||||
echo-area (Gtk.Label {
|
|
||||||
:xalign 0
|
|
||||||
:margin_start 10
|
|
||||||
})
|
|
||||||
window (Gtk.Window {
|
window (Gtk.Window {
|
||||||
:title "Dunlin"
|
:title "Dunlin"
|
||||||
:default_width 800
|
:default_width 800
|
||||||
@ -43,26 +35,16 @@
|
|||||||
:estimated-load-progress
|
:estimated-load-progress
|
||||||
(tset progress-bar :fraction value)
|
(tset progress-bar :fraction value)
|
||||||
:uri
|
:uri
|
||||||
(tset echo-area :label value)
|
(commander:set-inactive-text value)
|
||||||
n
|
n
|
||||||
(comment (print "prop change" n value))))]
|
(comment (print "prop change" n value))))]
|
||||||
|
|
||||||
(doto bottom-line
|
|
||||||
(: :add_named echo-area "echo-area")
|
|
||||||
(: :add_named commander.widget "commander")
|
|
||||||
(: :set_visible_child_name "commander"))
|
|
||||||
|
|
||||||
(doto container
|
|
||||||
(: :pack_start progress-bar false false vpad)
|
|
||||||
(: :pack_start contentwidget true true vpad)
|
|
||||||
(: :pack_end bottom-line false false vpad))
|
|
||||||
|
|
||||||
(tset window :on_key_release_event
|
(tset window :on_key_release_event
|
||||||
(fn [window event]
|
(fn [window event]
|
||||||
(when (not (commander:active?))
|
(when (not (commander:active?))
|
||||||
(match (recogniser:accept-event event)
|
(match (recogniser:accept-event event)
|
||||||
[name params] (commander:invoke-interactively name params)
|
[name params] (commander:invoke-interactively name params)
|
||||||
(nil prompt) (tset echo-area :label prompt)))
|
(nil prompt) (print "prompted" prompt)))
|
||||||
(when (and (commander:active?)
|
(when (and (commander:active?)
|
||||||
(= keymap.keyval.Escape event.keyval))
|
(= keymap.keyval.Escape event.keyval))
|
||||||
(commander:deactivate))
|
(commander:deactivate))
|
||||||
@ -71,9 +53,10 @@
|
|||||||
(= event.keyval (string.byte "x")))
|
(= event.keyval (string.byte "x")))
|
||||||
(commander:activate))))
|
(commander:activate))))
|
||||||
|
|
||||||
(echo-area:show) (commander.widget:show)
|
(doto container
|
||||||
|
(: :pack_start commander.widget false false vpad)
|
||||||
|
(: :pack_start progress-bar false false vpad)
|
||||||
|
(: :pack_start contentwidget true true vpad))
|
||||||
(window:add container)
|
(window:add container)
|
||||||
(window:show_all)
|
(window:show_all)
|
||||||
(let [f
|
(let [f
|
||||||
@ -81,8 +64,6 @@
|
|||||||
:window window
|
:window window
|
||||||
:buffer nil
|
:buffer nil
|
||||||
:content contentwidget
|
:content contentwidget
|
||||||
:message (fn [self message]
|
|
||||||
(tset echo-area :label message))
|
|
||||||
:show-buffer (fn [self b]
|
:show-buffer (fn [self b]
|
||||||
(each [_ w (pairs (contentwidget:get_children))]
|
(each [_ w (pairs (contentwidget:get_children))]
|
||||||
(w:hide))
|
(w:hide))
|
||||||
|
65
keymap.fnl
65
keymap.fnl
@ -1,6 +1,5 @@
|
|||||||
(local { : Gdk } (require :lgi))
|
(local { : Gdk } (require :lgi))
|
||||||
(local { : view } (require :fennel))
|
(local { : view } (require :fennel))
|
||||||
(local lume (require :lume))
|
|
||||||
|
|
||||||
(local modifier-keyvals
|
(local modifier-keyvals
|
||||||
;; we need to detect and discard modifier-only key events when
|
;; we need to detect and discard modifier-only key events when
|
||||||
@ -30,15 +29,15 @@
|
|||||||
(fn keychord->spec [keychord]
|
(fn keychord->spec [keychord]
|
||||||
(let [Mod Gdk.ModifierType
|
(let [Mod Gdk.ModifierType
|
||||||
symbol (keychord:match "(%w+)$")
|
symbol (keychord:match "(%w+)$")
|
||||||
; upper? (and (symbol:match "%u") true)
|
upper? (and (symbol:match "%u") true)
|
||||||
modmask (accumulate [m 0 ;(if upper? Mod.SHIFT_MASK 0)
|
modmask (accumulate [m (if upper? Mod.SHIFT_MASK 0)
|
||||||
v (keychord:gmatch "(%w+)-")]
|
v (keychord:gmatch "(%w+)-")]
|
||||||
(match (v:lower)
|
(match (v:lower)
|
||||||
"m" (bor m Mod.MOD1_MASK)
|
"m" (bor m Mod.MOD1_MASK)
|
||||||
"c" (bor m Mod.CONTROL_MASK)
|
"c" (bor m Mod.CONTROL_MASK)
|
||||||
"s" (bor m Mod.MOD4_MASK)))]
|
"s" (bor m Mod.MOD4_MASK)))]
|
||||||
{
|
{
|
||||||
:keyval (Gdk.keyval_from_name symbol)
|
:keyval (string.byte (symbol:lower))
|
||||||
: modmask
|
: modmask
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -52,25 +51,6 @@
|
|||||||
(bor m (. Gdk.ModifierType k)))]
|
(bor m (. Gdk.ModifierType k)))]
|
||||||
(spec->index {:keyval event.keyval : modmask})))
|
(spec->index {:keyval event.keyval : modmask})))
|
||||||
|
|
||||||
(fn compact [xs]
|
|
||||||
(icollect [_ v (ipairs xs)] v))
|
|
||||||
|
|
||||||
(fn index->string [index]
|
|
||||||
(let [Mod Gdk.ModifierType
|
|
||||||
[keyval modmask] (lume.map (lume.split index ":") tonumber)
|
|
||||||
chars []]
|
|
||||||
(if (> (band modmask Mod.CONTROL_MASK) 0) (table.insert chars "C"))
|
|
||||||
(if (> (band modmask Mod.MOD1_MASK) 0) (table.insert chars "M"))
|
|
||||||
(if (> (band modmask Mod.MOD4_MASK) 0) (table.insert chars "S"))
|
|
||||||
(table.insert chars (Gdk.keyval_name keyval))
|
|
||||||
(table.concat chars "-")))
|
|
||||||
|
|
||||||
(let [v (index->string "103:0")] (assert (= v "g") v))
|
|
||||||
(let [v (index->string "65:0")] (assert (= v "A") v))
|
|
||||||
(let [v (index->string "120:4")] (assert (= v "C-x") v))
|
|
||||||
(let [v (index->string "100:8")] (assert (= v "M-d") v))
|
|
||||||
(let [v (index->string "100:12")] (assert (= v "C-M-d") v))
|
|
||||||
|
|
||||||
(fn command? [tbl]
|
(fn command? [tbl]
|
||||||
;; a keymap entry has a string as key, a command
|
;; a keymap entry has a string as key, a command
|
||||||
;; definition is a numerically-indexed array
|
;; definition is a numerically-indexed array
|
||||||
@ -86,53 +66,40 @@
|
|||||||
(values f (compile-keymap v))
|
(values f (compile-keymap v))
|
||||||
(values f v)))))
|
(values f v)))))
|
||||||
|
|
||||||
(fn ref [tbl keys]
|
|
||||||
(when tbl
|
|
||||||
(match keys
|
|
||||||
[k1 & more] (ref (. tbl k1) more)
|
|
||||||
[k1] (. tbl k1)
|
|
||||||
x tbl)))
|
|
||||||
|
|
||||||
(let [v (ref {:a 1} [:a])] (assert (= v 1) v))
|
|
||||||
(let [v (ref {:a {:c 7}} [:a :c])] (assert (= v 7) v))
|
|
||||||
(let [v (ref {:a {:c 7}} [:a ])] (assert (match v {:c 7} true) (view v)))
|
|
||||||
(let [v (ref {:a {:c 7}} [:z :d])] (assert (not v) v))
|
|
||||||
|
|
||||||
|
|
||||||
(fn recogniser [source-keymap]
|
(fn recogniser [source-keymap]
|
||||||
(let [keymap (compile-keymap source-keymap)]
|
(let [keymap (compile-keymap source-keymap)]
|
||||||
(var key-sequence [])
|
(var m keymap)
|
||||||
{
|
{
|
||||||
:accept-event
|
:accept-event
|
||||||
(fn [_ e]
|
(fn [_ e]
|
||||||
(when (not (modifier? e.keyval))
|
(when (not (modifier? e.keyval))
|
||||||
(let [c (event->index e)]
|
(let [c (event->index e)]
|
||||||
(table.insert key-sequence c)
|
(match (. m c)
|
||||||
(match (ref keymap key-sequence)
|
|
||||||
(where v (keymap? v))
|
(where v (keymap? v))
|
||||||
(values nil
|
(do
|
||||||
(let [syms (lume.map key-sequence index->string)]
|
(set m v)
|
||||||
(table.concat syms " ")))
|
(values nil (.. c " ")))
|
||||||
|
|
||||||
(where v (command? v))
|
(where v (command? v))
|
||||||
(do
|
(do
|
||||||
(set key-sequence [])
|
(set m keymap)
|
||||||
v)
|
v)
|
||||||
|
|
||||||
(where nil (= c "103:4"))
|
(where nil (= c "103:4"))
|
||||||
(do
|
(do
|
||||||
(set key-sequence [])
|
(set m keymap)
|
||||||
(values nil "cancelled"))
|
(values nil "cancelled"))
|
||||||
_
|
_
|
||||||
(let [syms (lume.map key-sequence index->string)]
|
(do
|
||||||
(set key-sequence [])
|
(set m keymap)
|
||||||
(values nil (.. (table.concat syms " ") " is undefined")))))))
|
(values nil (.. "No binding for " (view e) " ")))))))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
{ : recogniser
|
{ : recogniser
|
||||||
:keyval (collect [_ name (ipairs [:Escape :Delete :BackSpace])]
|
:keyval {
|
||||||
(values name (Gdk.keyval_from_name name)))
|
:Escape (Gdk.keyval_from_name "Escape")
|
||||||
|
}
|
||||||
:_ {
|
:_ {
|
||||||
;; symbols in _ are exported only for testing
|
;; symbols in _ are exported only for testing
|
||||||
: keychord->spec
|
: keychord->spec
|
||||||
|
24
musing.md
24
musing.md
@ -79,28 +79,16 @@ focus from entry to step through the completions then RET activates
|
|||||||
* [done] show loading progress
|
* [done] show loading progress
|
||||||
* [done] show url when the commander is inactive
|
* [done] show url when the commander is inactive
|
||||||
* [done] visit-location url defaults to current
|
* [done] visit-location url defaults to current
|
||||||
* [done] ESC to cancel interactive command
|
|
||||||
* [done] C-g to cancel key sequence
|
|
||||||
|
|
||||||
* custom rendering for completions (e.g. buffer thumbnails)
|
* custom rendering for completions (e.g. buffer thumbnails)
|
||||||
* less ugly default completions rendering
|
* buffer name is often going to be useless. find buffers
|
||||||
* buffer name is often going to be useless. find buffers by url/title
|
by url/title
|
||||||
still need some 1:1 mapping between the buffer object and
|
still need some 1:1 mapping between the buffer object and
|
||||||
a text-representable form of same
|
a text-representable form of same
|
||||||
* click in commander widget activates visit-location
|
* click in commander widget activates visit-location
|
||||||
* in general, can we bind commands to widget events?
|
|
||||||
* display unbound key error
|
* display unbound key error
|
||||||
|
* ESC to cancel interactive command
|
||||||
* autocomplete command name
|
* autocomplete command name
|
||||||
* command to create new buffer
|
* multiple buffers
|
||||||
* keyboard navigation of completions
|
- create buffer
|
||||||
|
- list buffers (do we need this if we have thumbnails?)
|
||||||
----
|
|
||||||
|
|
||||||
I think we're misusing the commander to show url and error messages
|
|
||||||
and key prompts. It's OK to have that part of the screen be multipurpose
|
|
||||||
but philosophically those things are not related to the command system.
|
|
||||||
|
|
||||||
- hide commander when inactive and replace it with echo area
|
|
||||||
- move it to bottom?
|
|
||||||
|
|
||||||
commander can't hide itself, it needs to ask its parent to hide it
|
|
||||||
|
@ -33,9 +33,9 @@
|
|||||||
{:keyval 97 :modmask 4} true
|
{:keyval 97 :modmask 4} true
|
||||||
_ (assert false (view s))))
|
_ (assert false (view s))))
|
||||||
|
|
||||||
(let [s (keymap._.keychord->spec "C-M-z")]
|
(let [s (keymap._.keychord->spec "C-M-Z")]
|
||||||
(match s
|
(match s
|
||||||
{:keyval 122 :modmask 12} true
|
{:keyval 122 :modmask 13} true
|
||||||
_ (assert false (view s))))
|
_ (assert false (view s))))
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user