207 lines
7.2 KiB
Racket
207 lines
7.2 KiB
Racket
#lang racket/base
|
|
;; Modes and modesets.
|
|
|
|
(provide (struct-out mode)
|
|
(struct-out modeset)
|
|
(struct-out incomplete-key-sequence)
|
|
(struct-out unbound-key-sequence)
|
|
(struct-out command-invocation)
|
|
|
|
make-raw-mode
|
|
make-mode
|
|
mode-add-constraints
|
|
mode-keymap-bind!
|
|
mode-keymap-unbind!
|
|
mode-keymap-rebind!
|
|
mode-define-command!
|
|
mode-undefine-command!
|
|
mode-redefine-command!
|
|
|
|
make-modeset
|
|
modeset-add-mode
|
|
modeset-remove-mode
|
|
modeset-toggle-mode
|
|
modeset-keyseq-handler
|
|
modeset-lookup-command
|
|
|
|
kernel-mode
|
|
kernel-modeset)
|
|
|
|
(require racket/set)
|
|
(require racket/match)
|
|
(require (only-in racket/list filter-map))
|
|
|
|
(require "keys.rkt")
|
|
(require "topsort.rkt")
|
|
|
|
(struct mode (id
|
|
name
|
|
[keymap #:mutable]
|
|
[commands #:mutable]
|
|
dispatch-keys-before
|
|
dispatch-keys-after
|
|
interpret-commands-before
|
|
interpret-commands-after
|
|
) #:prefab)
|
|
|
|
(struct modeset (modes
|
|
key-dispatch-order
|
|
command-interpretation-order
|
|
) #:prefab)
|
|
|
|
(struct incomplete-key-sequence (handler) #:prefab)
|
|
(struct unbound-key-sequence () #:prefab)
|
|
(struct command-invocation (selector prefix-arg remaining-input) #:prefab)
|
|
|
|
(define (make-raw-mode name)
|
|
(mode (gensym name)
|
|
name
|
|
(empty-keymap)
|
|
(hasheq)
|
|
(seteq)
|
|
(seteq)
|
|
(seteq)
|
|
(seteq)))
|
|
|
|
(define (mode-add-constraints m
|
|
#:dispatch-keys-before [kb '()]
|
|
#:dispatch-keys-after [ka '()]
|
|
#:interpret-commands-before [cb '()]
|
|
#:interpret-commands-after [ca '()])
|
|
(define (convert modes) (list->seteq (for/list ((m modes))
|
|
(if (keyword? m)
|
|
m
|
|
(mode-id m)))))
|
|
(struct-copy mode m
|
|
[dispatch-keys-before
|
|
(set-union (mode-dispatch-keys-before m) (convert kb))]
|
|
[dispatch-keys-after
|
|
(set-union (mode-dispatch-keys-after m) (convert ka))]
|
|
[interpret-commands-before
|
|
(set-union (mode-interpret-commands-before m) (convert cb))]
|
|
[interpret-commands-after
|
|
(set-union (mode-interpret-commands-after m) (convert ca))]))
|
|
|
|
(define (make-mode name)
|
|
(mode-add-constraints (make-raw-mode name)
|
|
#:dispatch-keys-before '(#:kernel)
|
|
#:interpret-commands-before '(#:kernel)))
|
|
|
|
(define (mode-keymap-bind! m keyspec command)
|
|
(set-mode-keymap! m (keymap-bind (mode-keymap m) keyspec command))
|
|
m)
|
|
|
|
(define (mode-keymap-unbind! m keyspec)
|
|
(set-mode-keymap! m (keymap-unbind (mode-keymap m) keyspec))
|
|
m)
|
|
|
|
(define (mode-keymap-rebind! m keyspec command)
|
|
(mode-keymap-bind! (mode-keymap-unbind! m keyspec) keyspec command))
|
|
|
|
(define (mode-define-command! m selector handler)
|
|
(when (hash-has-key? (mode-commands m) selector)
|
|
(error 'mode-define-command!
|
|
"Duplicate command handler for ~a in mode ~a"
|
|
selector
|
|
(mode-id m)))
|
|
(set-mode-commands! m (hash-set (mode-commands m) selector handler))
|
|
m)
|
|
|
|
(define (mode-undefine-command! m selector)
|
|
(set-mode-commands! m (hash-remove (mode-commands m) selector))
|
|
m)
|
|
|
|
(define (mode-redefine-command! m selector handler)
|
|
(mode-define-command! (mode-undefine-command! m selector) selector handler))
|
|
|
|
(define (make-modeset)
|
|
(modeset (hasheq)
|
|
'()
|
|
'()))
|
|
|
|
(define (modeset-add-mode ms m)
|
|
(compute-modeset-orders
|
|
(struct-copy modeset ms [modes (hash-set (modeset-modes ms)
|
|
(mode-id m)
|
|
m)])))
|
|
|
|
(define (modeset-remove-mode ms m)
|
|
(compute-modeset-orders
|
|
(struct-copy modeset ms [modes (hash-remove (modeset-modes ms) (mode-id m))])))
|
|
|
|
(define (modeset-toggle-mode ms m)
|
|
((if (hash-has-key? (modeset-modes ms) (mode-id m)) modeset-remove-mode modeset-add-mode)
|
|
ms
|
|
m))
|
|
|
|
(define (edges ms before-getter after-getter)
|
|
(for/fold [(es '())]
|
|
[(m (in-hash-values (modeset-modes ms)))]
|
|
(define mid (mode-id m))
|
|
(append (for/list [(nid (before-getter m))] (list mid nid))
|
|
(for/list [(nid (after-getter m))] (list nid mid))
|
|
es)))
|
|
|
|
(define (compute-modeset-order ms what before-getter after-getter)
|
|
(or (topsort (edges ms before-getter after-getter) #:comparison eq?)
|
|
(error 'compute-modeset-orders "Inconsistent ~a order: ~v"
|
|
(hash-keys (modeset-modes ms)))))
|
|
|
|
(define (compute-modeset-orders ms)
|
|
(struct-copy modeset ms
|
|
[key-dispatch-order (compute-modeset-order ms
|
|
"key dispatch"
|
|
mode-dispatch-keys-before
|
|
mode-dispatch-keys-after)]
|
|
[command-interpretation-order (compute-modeset-order ms
|
|
"command interpretation"
|
|
mode-interpret-commands-before
|
|
mode-interpret-commands-after)]))
|
|
|
|
(define (order->modes ms order-getter)
|
|
(define modes (modeset-modes ms))
|
|
(filter-map (lambda (id) (hash-ref modes id #f)) (order-getter ms)))
|
|
|
|
(define (modeset-keyseq-handler ms)
|
|
(let handler-for-maps ((maps (map mode-keymap (order->modes ms modeset-key-dispatch-order))))
|
|
(lambda (e ks)
|
|
(define results (map (lambda (km)
|
|
(define-values (result remaining-input) (keymap-lookup km ks))
|
|
(list result remaining-input)) maps))
|
|
(let process-results ((results results))
|
|
(match results
|
|
['() (unbound-key-sequence)]
|
|
[(cons (list result remaining-input) rest)
|
|
(cond
|
|
[(not result) (process-results rest)]
|
|
[(keymap? result) (incomplete-key-sequence
|
|
(handler-for-maps (filter keymap? (map car results))))]
|
|
[(procedure? result)
|
|
(if (null? remaining-input)
|
|
(incomplete-key-sequence result)
|
|
(result e remaining-input))]
|
|
[else (command-invocation result '#:default remaining-input)])])))))
|
|
|
|
(define (modeset-lookup-command ms selector)
|
|
(let search ((tables (map mode-commands
|
|
(order->modes ms modeset-command-interpretation-order))))
|
|
(match tables
|
|
['() #f]
|
|
[(cons table rest)
|
|
(define handler (hash-ref table selector #f))
|
|
(if handler
|
|
(lambda (cmd)
|
|
(handler cmd
|
|
(lambda ([cmd cmd])
|
|
(define next-method (search rest))
|
|
(when next-method (next-method cmd)))))
|
|
(search rest))])))
|
|
|
|
(define kernel-mode
|
|
(mode-add-constraints (make-raw-mode "kernel")
|
|
#:dispatch-keys-after '(#:kernel)
|
|
#:interpret-commands-after '(#:kernel)))
|
|
|
|
(define kernel-modeset
|
|
(modeset-add-mode (make-modeset) kernel-mode))
|