Compare commits

..

No commits in common. "da06309e759171fd5bb2f56b261b3033b517a05d" and "6efbc3457643f09a46f5df295b5ea52a6d831855" have entirely different histories.

12 changed files with 133 additions and 268 deletions

1
.gitignore vendored
View File

@ -1 +0,0 @@
doc/index.html

View File

@ -1 +0,0 @@
OVERMIND_AUTO_RESTART=test

View File

@ -2,4 +2,4 @@ doc/index.html: doc/index.md
pandoc -t html -f gfm < $< > $@ pandoc -t html -f gfm < $< > $@
watch: watch:
find . -type f | entr -c -d sh test/run.sh while true ; do ( find . -type f | entr -d sh test/run.sh ) ;done

View File

@ -33,14 +33,6 @@
(let [b (new-buffer name)] (let [b (new-buffer name)]
(tset buffers name b) (tset buffers name b)
b)) b))
:current (fn [] (let [k (next buffers)] (. buffers k)))
:find (fn [term] (. buffers term)) :find (fn [term] (. buffers term))
;; will rename this to "find" once we've got rid of the
;; only remaining call to the existing Buffer.find
:match (fn [s] (collect [name buffer (pairs buffers)]
(if (string.find name s)
(values name buffer))))
:next (fn [buffer]
(let [n (or (next buffers buffer.name) (next buffers))]
(. buffers n)))
}) })

View File

@ -1,16 +1,23 @@
(local { : Gtk } (require :lgi)) (local { : Gtk } (require :lgi))
(local { : view } (require :fennel)) (local { : view } (require :fennel))
(local lume (require :lume))
(local commands {}) (local commands {})
(local Buffer (require :buffer)) (local Buffer (require :buffer))
(fn define-command [name ordered-params function] (fn by-pairs [a]
(let [iter (fn [_ a]
(match a
[k v & rest] (values rest k v)
_ nil))]
(values iter a a)))
(fn define-command [name function ordered-params]
;; required parameter names and default arguments ;; required parameter names and default arguments
(let [param-names (icollect [_ [name] (pairs ordered-params)] name) (let [param-names (icollect [_ name val (by-pairs ordered-params)]
params (collect [_ [name completer default] (pairs ordered-params)] name)
(values name {: completer : default})) params (collect [_ name val (by-pairs ordered-params)]
(values name val))
v {: name v {: name
: function : function
: param-names : param-names
@ -19,27 +26,15 @@
(define-command (define-command
"quit-browser" "quit-browser"
[] #(Gtk.main_quit) [])
#(Gtk.main_quit))
(define-command
"switch-to-buffer"
[[:buffer
Buffer.match
#(. (Buffer.next $1.buffer) :name)]
]
(fn [{:frame frame :buffer buffer}]
(frame:show-buffer buffer)))
(define-command (define-command
"visit-location" "visit-location"
[[:buffer
Buffer.match
#($1.buffer.name)]
[:url #(doto {} (tset $1 $1)) #(do "http://www.example.com")]
]
(fn [{:url url :buffer buffer}] (fn [{:url url :buffer buffer}]
(buffer:visit url))) (let [b (Buffer.find buffer)] (: b :visit url)))
[:buffer (fn [] (. (Buffer.current) :name))
:url #(do "http://www.example.com")
])
(fn find-command [name] (fn find-command [name]
(. commands name)) (. commands name))
@ -51,6 +46,10 @@
:this-param nil :this-param nil
}) })
(var state default-state)
(fn reset-state []
(set state default-state))
(fn next-param [command params] (fn next-param [command params]
(accumulate [v nil (accumulate [v nil
_ k (ipairs command.param-names) _ k (ipairs command.param-names)
@ -60,9 +59,8 @@
(fn invoke-command [command params] (fn invoke-command [command params]
(command.function params)) (command.function params))
(fn next-action [self input-string] (fn next-action [state input-string]
(let [state self.state (let [state-for-next-param
state-for-next-param
(fn [c params] (fn [c params]
(match (next-param c params) (match (next-param c params)
k1 { k1 {
@ -71,9 +69,7 @@
:collected-params params :collected-params params
:active true :active true
} }
_ (let [params (lume.extend {} {:frame self.frame} params)] _ (do (invoke-command c params) {:active false})))]
(invoke-command c params)
{:active false})))]
(match state (match state
{:active false} state {:active false} state
@ -90,10 +86,8 @@
}) })
{:command c :this-param k :collected-params p} {:command c :this-param k :collected-params p}
(let [{ : completer} (. c.params k) (do
vals (completer input-string) (tset p k input-string)
value (. vals input-string)]
(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}
@ -104,102 +98,75 @@
state) state)
))) )))
(fn on-activate [self str] (fn on-input [str]
(let [s (next-action self str) (let [s (next-action state str)
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 state s)
{ {
:active s.active :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))
})) }))
(fn update-widget-state [{ : entry : completions-widget : prompt} result] (local prompt (Gtk.Label { :label ""}))
(set prompt.label (or result.prompt ""))
(set entry.sensitive result.active)
(if (not result.active)
(completions-widget:hide))
(set entry.text (or result.default result.error "")))
(fn on-input [self str] (fn update-widget-state [w result]
(match self.state (set prompt.label (or result.prompt ""))
{:command c :this-param param-name} (set w.sensitive result.active)
(let [parent self.completions-widget (set w.text
{ : completer} (. c.params param-name) (or result.default result.error "")))
completions (completer str)]
(parent:foreach #(parent:remove $1))
(each [text _w (pairs completions)]
(parent:add (Gtk.Button {
:label text
:on_clicked
#(update-widget-state self (self:on-activate text))
})))
(parent:show_all)
)))
(local widget
(let [w (Gtk.Entry {
:sensitive false
})]
(tset w :on_activate
(fn [event]
(update-widget-state w (on-input event.text))))
w))
(fn activate [{: state : entry : prompt}] (local box
(let [box
(Gtk.Box {
:orientation Gtk.Orientation.HORIZONTAL
})]
(box:pack_start prompt false false 15)
(box:pack_start widget true true 5)
box))
(fn activate []
(tset state :active true) (tset state :active true)
(set entry.sensitive true) (set widget.sensitive true)
(set entry.text "") (set widget.text "")
(set prompt.label (or state.this-param "Command" "")) (set prompt.label (or state.this-param "Command" ""))
(entry:grab_focus) (widget:grab_focus)
state) state)
(fn invoke-interactively [self name params] (fn invoke-interactively [name params]
(let [c (find-command name) (let [c (find-command name)
supplied-params (collect [k v (pairs params)]
(values k (v self.frame)))
s { s {
:active true :active true
:command c :command c
:collected-params supplied-params :collected-params params
}] }]
(set self.state s) (set state s)
(let [r (self:on-activate nil)] (let [r (on-input nil)]
(update-widget-state self r) (update-widget-state widget r)
(self.entry:grab_focus) (widget:grab_focus)
r))) r)))
(fn new-commander [frame] (fn active? [] state.active)
(let [entry (Gtk.Entry {:sensitive false })
prompt (Gtk.Label { :label ""})
box (Gtk.Box {
:orientation Gtk.Orientation.VERTICAL
})
hbox (Gtk.Box {
:orientation Gtk.Orientation.HORIZONTAL
})
completions (Gtk.FlowBox)
self {
:state default-state
: activate
:active? (fn [self] self.state.active)
: on-input
: on-activate
: invoke-interactively
: entry
:widget box
: prompt
: frame
:completions-widget completions
}]
(hbox:pack_start prompt false false 15)
(hbox:pack_start entry true true 5)
(box:pack_start hbox true false 0)
(box:pack_start completions true true 0)
(tset entry :on_changed
(fn [event]
(self:on-input event.text)))
(tset entry :on_activate
(fn [event]
(let [result (self:on-activate event.text)]
(update-widget-state self result))))
self))
{ {
:commander new-commander : activate
: active?
: define-command : define-command
: on-input
: invoke-interactively
:widget box
:_ {
: reset-state
}
} }

View File

@ -19,26 +19,11 @@
, writeText , writeText
}: }:
let pname = "dunlin"; let pname = "dunlin";
lume = let lua = lua5_3; in lua53Packages.buildLuaPackage rec {
pname = "lume";
version = "1";
src = fetchFromGitHub {
repo = "lume"; owner = "rxi";
rev = "98847e7812cf28d3d64b289b03fad71dc704547d";
hash = "sha256-/u23EqgjjkU8FV9oXvMNXBkY8JAOJUhJAzXTSibJthU=";
};
buildPhase = ":";
installPhase = ''
mkdir -p "$out/share/lua/${lua.luaversion}"
cp lume.lua "$out/share/lua/${lua.luaversion}"
'';
};
lua = lua5_3.withPackages (ps: with ps; [ lua = lua5_3.withPackages (ps: with ps; [
lgi lgi
luafilesystem luafilesystem
luaposix luaposix
readline readline
lume
]); ]);
fennel_ = lua.pkgs.fennel; fennel_ = lua.pkgs.fennel;
glib_networking_gio = "${glib-networking}/lib/gio/modules"; glib_networking_gio = "${glib-networking}/lib/gio/modules";

View File

@ -9,12 +9,18 @@
;;; when we decide how to do an init file/rc file, this will go in it ;;; when we decide how to do an init file/rc file, this will go in it
(local my-keymap { (local my-keymap {
"g" ["visit-location" {:buffer #$1.buffer }] "g" #(Command.invoke-interactively
"M-q" ["quit-browser" {}] "visit-location"
{:buffer "main"})
"M-q" #(Command.invoke-interactively
"quit-browser"
{})
"C-x" { "C-x" {
"C-c" ["quit-browser" {}] "C-c"
"b" ["switch-to-buffer" {}] #(Command.invoke-interactively
} "quit-browser"
{})
}
}) })
(let [f (Frame.new my-keymap) (let [f (Frame.new my-keymap)

View File

@ -1,7 +1,5 @@
(local { : Gtk : Gdk : WebKit2 : cairo } (require :lgi)) (local { : Gtk : Gdk : WebKit2 : cairo } (require :lgi))
(local { : view } (require :fennel)) (local { : view } (require :fennel))
(local lume (require :lume))
(local Command (require :command)) (local Command (require :command))
(local keymap (require :keymap)) (local keymap (require :keymap))
@ -10,9 +8,7 @@
(fn new-frame [global-keymap] (fn new-frame [global-keymap]
(let [hpad 2 (let [hpad 2
vpad 2 vpad 2
self {}
recogniser (keymap.recogniser global-keymap) recogniser (keymap.recogniser global-keymap)
commander (Command.commander self)
window (Gtk.Window { window (Gtk.Window {
:title "Dunlin" :title "Dunlin"
:default_width 800 :default_width 800
@ -34,16 +30,16 @@
(tset window :on_key_release_event (tset window :on_key_release_event
(fn [window event] (fn [window event]
(when (not (commander:active?)) (when (not (Command.active?))
(match (recogniser:accept-event event) (match (recogniser:accept-event event)
[name params] (commander:invoke-interactively name params) c (c)
(nil prompt) (print "prompted" prompt))) (nil prompt) (print "prompted" prompt)))
(when (and event.state.MOD1_MASK (when (and event.state.MOD1_MASK
(= event.keyval (string.byte "x"))) (= event.keyval (string.byte "x")))
(commander:activate)))) (Command.activate))))
(doto container (doto container
(: :pack_start commander.widget false false vpad) (: :pack_start Command.widget false false vpad)
(: :pack_start progress-bar false false vpad) (: :pack_start progress-bar false false vpad)
(: :pack_start contentwidget true true vpad)) (: :pack_start contentwidget true true vpad))
(window:add container) (window:add container)
@ -56,13 +52,11 @@
: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))
(tset self :buffer b)
(contentwidget:pack_start b.webview true true 0) (contentwidget:pack_start b.webview true true 0)
(b.webview:show)) (b.webview:show))
}] }]
(lume.extend self f) (table.insert frames f)
(table.insert frames self) f)))
self)))
{ :new new-frame :frames frames } { :new new-frame :frames frames }

View File

@ -1,24 +1,6 @@
(local { : Gdk } (require :lgi)) (local { : Gdk } (require :lgi))
(local { : view } (require :fennel)) (local { : view } (require :fennel))
(local modifier-keyvals
{
;; These aren't canonical or official, this is just the
;; result of pressing keys on my keyboard. If Gtk/Gdk/GI
;; implemented KeyEvent.is_modifier we wouldn't have to
;; do this
65507 :control_l
65505 :shift_l
269025067 :fn
65515 :windows
65513 :alt_l
65027 :alt_gr
65508 :control_r
})
(fn modifier? [keyval]
(. modifier-keyvals keyval))
(fn keychord->spec [keychord] (fn keychord->spec [keychord]
(let [Mod Gdk.ModifierType (let [Mod Gdk.ModifierType
symbol (keychord:match "(%w+)$") symbol (keychord:match "(%w+)$")
@ -44,17 +26,13 @@
(bor m (. Gdk.ModifierType k)))] (bor m (. Gdk.ModifierType k)))]
(spec->index {:keyval event.keyval : modmask}))) (spec->index {:keyval event.keyval : modmask})))
(fn designates-command? [tbl]
;; a keymap entry has a string as key, a command
;; definition is a numerically-indexed array
(if (. tbl 1) true))
(fn compile-keymap [input] (fn compile-keymap [input]
(collect [k v (pairs input)] (collect [k v (pairs input)]
(let [f (-> k keychord->spec spec->index)] (let [f (-> k keychord->spec spec->index)]
(if (designates-command? v) (match (type v)
(values f v) "function" (values f v)
(values f (compile-keymap v)))))) "table" (values f (compile-keymap v))))))
(fn recogniser [source-keymap] (fn recogniser [source-keymap]
(let [keymap (compile-keymap source-keymap)] (let [keymap (compile-keymap source-keymap)]
@ -62,20 +40,20 @@
{ {
:accept-event :accept-event
(fn [_ e] (fn [_ e]
(when (not (modifier? e.keyval)) (let [c (event->index e)
(let [c (event->index e) v (. m c)]
v (. m c)] (match (type v)
(if v "table" (do
(if (designates-command? v)
(do
(set m keymap)
v)
(do
(set m v) (set m v)
(values nil (.. c " ")))) (values nil (.. c " ")))
(do "function" (do
(set m keymap) (set m keymap)
(values nil (.. "No binding for " (view e) " "))))))) v)
"nil" (do
(set m keymap)
(values nil (.. "No binding for " (view e) " ")))
)))
})) }))

View File

@ -36,52 +36,14 @@ lua's standard types
## next steps ## next steps
* [done] change define-command so that the parameters are ordered * change define-command so that the parameters are ordered
* display unbound key error * display unbound key error
* ESC to cancel interactive command * ESC to cancel interactive command
* autocomplete command name * autocomplete command name
* parameters with non-string values (e.g. buffer) * parameters with non-string values (e.g. buffer)
* show current url when command inactive * show current url when command inactive
* [done] show prompts for parameter * show prompts for parameter
* multiple buffers * multiple buffers
- create buffer - create buffer
- list buffers (where does the output go?) - list buffers (where does the output go?)
- find and switch to buffer - find and switch to buffer
how do we do the buffer list thing?
- generate html, or
- use native widgets
native widgets seems neater
- how do we permit commands to insert widgets into the frame?
- how do we get rid of them?
we could have an "output overlay" inserted underneath the commander.
could we use the same thing for completions? we haven't addressed
non-string parameters yet, really
M-x switch-to-buffer
Buffer mai_
+------+ +---------+
| main | | mailing |
+------+ +---------+
-----
so there are two things going on here
1) how to implement switch-to-buffer with appropriate autocomplete
on the buffer name - perhaps involving showing buffer thumbnails etc
2) in emacs, not all buffers are files - e.g. the buffer list, or the
process list, or the magit status buffer - there is a well-used
affordance for elisp to put semi-persistent interactable content
onscreen - do we need such a thing here or is it ok to say "just call
gtk" to command authors
are these the same problem or are they separate problems? do we have
the second problem? What I will do is address the first one and
see if it's generalisable once I've done it.

View File

@ -3,48 +3,41 @@
(local Command (require :command)) (local Command (require :command))
(var happened false) (var happened false)
(fn before [] (set happened false)) (fn before [] (set happened false) (Command._.reset-state))
(Command.define-command (Command.define-command "no-args-command" #(set happened true) [])
"no-args-command"
[]
#(set happened true))
(Command.define-command (Command.define-command
"multiply" "multiply"
[[:a #{$1 $1} #"3"] (fn [{: a : b }] (set happened (* (tonumber a) (tonumber b))))
[:b #{$1 $1} #"2"]] [:a #(do "3") :b #(do "2")])
(fn [{: a : b }] (set happened (* (tonumber a) (tonumber b)))))
(before) (before)
(let [commander (Command.commander) (let [(ok err)
(ok err) (match-try (Command.activate)
(match-try (commander:activate) {:active true} (Command.on-input "not-a-command")
{:active true} (commander:on-activate "not-a-command")
(where {:error e :active false} (e:match "can't find command")) true (where {:error e :active false} (e:match "can't find command")) true
(catch (catch
x (values nil (view x))))] x (values nil (view x))))]
(assert ok err)) (assert ok err))
(before) (before)
(let [commander (Command.commander) (let [(ok err)
(ok err) (match-try (Command.activate)
(match-try (commander:activate) {:active true} (Command.on-input "multiply")
{:active true} (commander:on-activate "multiply") {:active true :prompt p1} (Command.on-input "2")
{:active true :prompt p1} (commander:on-activate "2") {:active true :prompt p2} (Command.on-input "3")
{:active true :prompt p2} (commander:on-activate "3")
(where {:active false} (= happened 6)) true (where {:active false} (= happened 6)) true
(catch (catch
x (values nil (view x))))] x (values nil (view x))))]
(assert ok err)) (assert ok err))
(before) (before)
(let [commander (Command.commander) (let [(ok err)
(ok err)
(match (match
(commander:invoke-interactively (Command.invoke-interactively
"multiply" "multiply"
{:a #"7" :b #"9"}) {:a "7" :b "9"})
(where {:active false} (= happened 63)) true (where {:active false} (= happened 63)) true
x (values nil (.. "wrong answer " (view x) " " (view happened))) x (values nil (.. "wrong answer " (view x) " " (view happened)))
nil (values nil "???"))] nil (values nil "???"))]

View File

@ -6,15 +6,13 @@
(local Mod Gdk.ModifierType) (local Mod Gdk.ModifierType)
(local km { (local km
"a" { {"a"
"a" ["command-1"] {"a" #1
"b" ["command-2" {:arg-1 "10" :arg-2 "11"}] "b" #2}
} "b"
"b" { {"z" #3}
"z" ["command-3"] "c" #4
}
"c" ["command-4"]
}) })
(fn fake-key-event [c] (fn fake-key-event [c]
@ -42,16 +40,8 @@
(let [r (keymap.recogniser km) (let [r (keymap.recogniser km)
(ok err) (ok err)
(match (r:accept-event (fake-key-event "c")) (match (r:accept-event (fake-key-event "c"))
["command-4"] true (where f (= (f) 4)) true
x (values false (view x)) x (values false (view x))
nil (values false "???"))] nil (values false "???"))]
(assert ok err))
(let [r (keymap.recogniser km)
(ok err)
(match-try
(r:accept-event (fake-key-event "a"))
nil (r:accept-event (fake-key-event "b"))
["command-2" {:arg-1 "10" :arg-2 "11"}] true
(catch x (values false (view x))))]
(assert ok err)) (assert ok err))