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 < $< > $@
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)]
(tset buffers name b)
b))
:current (fn [] (let [k (next buffers)] (. buffers k)))
: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 { : view } (require :fennel))
(local lume (require :lume))
(local commands {})
(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
(let [param-names (icollect [_ [name] (pairs ordered-params)] name)
params (collect [_ [name completer default] (pairs ordered-params)]
(values name {: completer : default}))
(let [param-names (icollect [_ name val (by-pairs ordered-params)]
name)
params (collect [_ name val (by-pairs ordered-params)]
(values name val))
v {: name
: function
: param-names
@ -19,27 +26,15 @@
(define-command
"quit-browser"
[]
#(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)))
#(Gtk.main_quit) [])
(define-command
"visit-location"
[[:buffer
Buffer.match
#($1.buffer.name)]
[:url #(doto {} (tset $1 $1)) #(do "http://www.example.com")]
]
(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]
(. commands name))
@ -51,6 +46,10 @@
:this-param nil
})
(var state default-state)
(fn reset-state []
(set state default-state))
(fn next-param [command params]
(accumulate [v nil
_ k (ipairs command.param-names)
@ -60,9 +59,8 @@
(fn invoke-command [command params]
(command.function params))
(fn next-action [self input-string]
(let [state self.state
state-for-next-param
(fn next-action [state input-string]
(let [state-for-next-param
(fn [c params]
(match (next-param c params)
k1 {
@ -71,9 +69,7 @@
:collected-params params
:active true
}
_ (let [params (lume.extend {} {:frame self.frame} params)]
(invoke-command c params)
{:active false})))]
_ (do (invoke-command c params) {:active false})))]
(match state
{:active false} state
@ -90,10 +86,8 @@
})
{:command c :this-param k :collected-params p}
(let [{ : completer} (. c.params k)
vals (completer input-string)
value (. vals input-string)]
(tset p k value)
(do
(tset p k input-string)
(state-for-next-param c p))
{:command c :this-param nil :collected-params p}
@ -104,102 +98,75 @@
state)
)))
(fn on-activate [self str]
(let [s (next-action self str)
(fn on-input [str]
(let [s (next-action state str)
param (if s.active (. (. s.command :params) s.this-param))]
(set self.state s)
(set state s)
{
:active s.active
:error s.error
: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]
(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 "")))
(local prompt (Gtk.Label { :label ""}))
(fn on-input [self str]
(match self.state
{:command c :this-param param-name}
(let [parent self.completions-widget
{ : completer} (. c.params param-name)
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)
)))
(fn update-widget-state [w result]
(set prompt.label (or result.prompt ""))
(set w.sensitive result.active)
(set w.text
(or result.default result.error "")))
(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)
(set entry.sensitive true)
(set entry.text "")
(set widget.sensitive true)
(set widget.text "")
(set prompt.label (or state.this-param "Command" ""))
(entry:grab_focus)
(widget:grab_focus)
state)
(fn invoke-interactively [self name params]
(fn invoke-interactively [name params]
(let [c (find-command name)
supplied-params (collect [k v (pairs params)]
(values k (v self.frame)))
s {
:active true
:command c
:collected-params supplied-params
:collected-params params
}]
(set self.state s)
(let [r (self:on-activate nil)]
(update-widget-state self r)
(self.entry:grab_focus)
(set state s)
(let [r (on-input nil)]
(update-widget-state widget r)
(widget:grab_focus)
r)))
(fn new-commander [frame]
(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))
(fn active? [] state.active)
{
:commander new-commander
: activate
: active?
: define-command
: on-input
: invoke-interactively
:widget box
:_ {
: reset-state
}
}

View File

@ -19,26 +19,11 @@
, writeText
}:
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; [
lgi
luafilesystem
luaposix
readline
lume
]);
fennel_ = lua.pkgs.fennel;
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
(local my-keymap {
"g" ["visit-location" {:buffer #$1.buffer }]
"M-q" ["quit-browser" {}]
"g" #(Command.invoke-interactively
"visit-location"
{:buffer "main"})
"M-q" #(Command.invoke-interactively
"quit-browser"
{})
"C-x" {
"C-c" ["quit-browser" {}]
"b" ["switch-to-buffer" {}]
}
"C-c"
#(Command.invoke-interactively
"quit-browser"
{})
}
})
(let [f (Frame.new my-keymap)

View File

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

View File

@ -1,24 +1,6 @@
(local { : Gdk } (require :lgi))
(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]
(let [Mod Gdk.ModifierType
symbol (keychord:match "(%w+)$")
@ -44,17 +26,13 @@
(bor m (. Gdk.ModifierType k)))]
(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]
(collect [k v (pairs input)]
(let [f (-> k keychord->spec spec->index)]
(if (designates-command? v)
(values f v)
(values f (compile-keymap v))))))
(match (type v)
"function" (values f v)
"table" (values f (compile-keymap v))))))
(fn recogniser [source-keymap]
(let [keymap (compile-keymap source-keymap)]
@ -62,20 +40,20 @@
{
:accept-event
(fn [_ e]
(when (not (modifier? e.keyval))
(let [c (event->index e)
v (. m c)]
(if v
(if (designates-command? v)
(do
(set m keymap)
v)
(do
(let [c (event->index e)
v (. m c)]
(match (type v)
"table" (do
(set m v)
(values nil (.. c " "))))
(do
(set m keymap)
(values nil (.. "No binding for " (view e) " ")))))))
(values nil (.. c " ")))
"function" (do
(set m keymap)
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
* [done] change define-command so that the parameters are ordered
* change define-command so that the parameters are ordered
* display unbound key error
* ESC to cancel interactive command
* autocomplete command name
* parameters with non-string values (e.g. buffer)
* show current url when command inactive
* [done] show prompts for parameter
* show prompts for parameter
* multiple buffers
- create buffer
- list buffers (where does the output go?)
- 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))
(var happened false)
(fn before [] (set happened false))
(fn before [] (set happened false) (Command._.reset-state))
(Command.define-command
"no-args-command"
[]
#(set happened true))
(Command.define-command "no-args-command" #(set happened true) [])
(Command.define-command
"multiply"
[[:a #{$1 $1} #"3"]
[:b #{$1 $1} #"2"]]
(fn [{: a : b }] (set happened (* (tonumber a) (tonumber b)))))
(fn [{: a : b }] (set happened (* (tonumber a) (tonumber b))))
[:a #(do "3") :b #(do "2")])
(before)
(let [commander (Command.commander)
(ok err)
(match-try (commander:activate)
{:active true} (commander:on-activate "not-a-command")
(let [(ok err)
(match-try (Command.activate)
{:active true} (Command.on-input "not-a-command")
(where {:error e :active false} (e:match "can't find command")) true
(catch
x (values nil (view x))))]
(assert ok err))
(before)
(let [commander (Command.commander)
(ok err)
(match-try (commander:activate)
{:active true} (commander:on-activate "multiply")
{:active true :prompt p1} (commander:on-activate "2")
{:active true :prompt p2} (commander:on-activate "3")
(let [(ok err)
(match-try (Command.activate)
{:active true} (Command.on-input "multiply")
{:active true :prompt p1} (Command.on-input "2")
{:active true :prompt p2} (Command.on-input "3")
(where {:active false} (= happened 6)) true
(catch
x (values nil (view x))))]
(assert ok err))
(before)
(let [commander (Command.commander)
(ok err)
(let [(ok err)
(match
(commander:invoke-interactively
(Command.invoke-interactively
"multiply"
{:a #"7" :b #"9"})
{:a "7" :b "9"})
(where {:active false} (= happened 63)) true
x (values nil (.. "wrong answer " (view x) " " (view happened)))
nil (values nil "???"))]

View File

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