[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[STUMP] manual addition part 2: commands and types
From: |
Eric Abrahamsen |
Subject: |
[STUMP] manual addition part 2: commands and types |
Date: |
Fri, 21 Mar 2014 21:03:37 +0800 |
User-agent: |
Gnus/5.13001 (Ma Gnus v0.10) Emacs/24.3 (gnu/linux) |
Okay, here's the next new bit for the manual: Chapter 3 on commands and
custom types. This was pretty interesting to research. I'll leave it
here for a week or so for comments, then create a pull request. The docs
as I've written them depend on the last two patches I sent to the list
-- if there's a problem with those, it would be easy to adjust back.
TL;DR: I have a couple of outstanding questions -- if anyone who's done
command spelunking has any experience to share, that would be great.
1. Command arguments are given as strings, and most types do a good job
of parsing strings into whatever vlaue they actually need. I can't see
how :key and :key-seq can parse multiple keypresses, though. What I mean
is, if :key-seq prompts for a value, you can enter it fine in the input
window. But if you try to pass it as part of the argument line, I can
make eg "C-t" work, but not "C-t n" (or "C-tn" or "C-t\n" or...). It's
not that big a deal, but if there's a string notation that would allow
more complex keypresses to be parsed, I'd like to know.
2. Command names can be given as a list as well as a symbol. If a list,
the first element is used as the command name. Looking at existing code
gives the impression that the second element is meant to be some sort of
method dispatch mechanism, but doesn't actually appear to be implemented
at the moment. Does anyone know if this actually does anything now?
3. The `colon' command is bound to the semicolon, not the colon. Just
saying.
Thanks!
Eric
Writing Commands
────────────────
StumpWM commands are written much like any Lisp function. The main
difference is in the way command arguments are specified. The
`defcommand’ macro takes a list of arguments as its first form
(similar to the `defun’ macro), and a corresponding list of types as
its second form. All arguments must belong to a “type”. Each type
specification has two parts: a keyword specifying the argument type,
and a string prompt that will be displayed when asking the user to
enter the argument value. A typical `defcommand’ might look like this:
╭────
│ (defcommand now-we-are-six (name age)
│ ((:string "Enter your name: ")
│ (:number "Enter your age: "))
│ (message "In six years, ~a will be ~a" name (+ 6 age)))
╰────
If `now-we-are-six’ is called interactively via the `colon’ command,
the user will be prompted for a string and a number, which will then
be bound to “name” and “age”, respectively, in the body of the
command.
When invoking the command via a keybinding, it is possible to provide
some or all of the arguments directly:
`(define-key *root-map* (kbd "L") "now-we-are-six john")'
In this case, hitting “C-t L” will only prompt for an age (the first
string argument is already bound to “john”). Argument values provided
this way always bind to the earliest arguments defined: ie, it is not
possible to specify an age, but prompt the user for a name.
If the type declaration does not include a prompt (ie it looks like
"(:type nil)", or "(:type)" or just “:type”), the argument is
considered optional. It can be provided via a key-binding invocation,
as above, but if it isn’t, the user will not be prompted, and the
argument will be bound to nil.
StumpWM Types
─────────────
All command arguments must be of a defined “StumpWM type”. The
following types are pre-defined: [this list doesn’t need to be both
here and in “Miscellaneous Commands”. I’m leaning toward listing types
here, and having a link to this spot from misc. commands]
:Y-OR-N A yes or no question returning T or NIL.
:VARIABLE A lisp variable
:FUNCTION A lisp function
:COMMAND A stumpwm command as a string.
:KEY-SEQ A key sequence starting from *TOP-MAP*
:WINDOW-NUMBER An existing window number
:NUMBER An integer number
:STRING A string
:KEY A single key chord
:WINDOW-NAME An existing window’s name
:DIRECTION A direction symbol. One of :UP :DOWN :LEFT :RIGHT
:GRAVITY A gravity symbol. One of :center :top :right :bottom :left
:top-right :top-left :bottom-right :bottom-left
:GROUP An existing group
:FRAME A frame
:SHELL A shell command
:REST The rest of the input yes to be parsed.
:MODULE An existing stumpwm module
Additional types can be defined using the macro `define-stumpwm-type’.
Emacs users who are accustomed to writing more complicated interactive
declarations using "(interactive (list …))" forms will find that
similar logic can be put into StumpWM type definitions. The macro is
called like this:
(define-stumpwm-type :type-name (input prompt) body)
The keyword :type-name will then be available for use in `defcommand’
macros. When commands are called, the bodies of these type definitions
are called in turn to produce actual argument values.
Type definitions produce their value in one of several ways: by
reading it from the argument line bound to a keystroke, by prompting
the user to enter a value, or by generating it programmatically.
Within the body of the type definition, the argument “input” is bound
to the argument line provided in the command string, and “prompt” to
the string prompt provided in the `defcommand’ form. The usual
convention is to first check if an argument has been provided in
“input”, and if not, to prompt for it using “prompt”.
StumpWM provides several convenience functions for handling the value
of “input”:
• `argument-pop’ (input) pop the next space-delimited argument from
the argument line.
• `argument-pop-rest’ (input) return the remainder of the argument
line as a single string, leaving input empty
• `argument-pop-or-read’ (input prompt &optional completions) either
pop an argument from the argument line, or if it is empty use
`prompt’ to prompt the user for a value
• `argument-pop-rest-or-read’ (input prompt &optional completions)
either return the remainder of the argument line as a string,
leaving input empty, or use `prompt’ to prompt the user for a value
As an example, here’s a new type called :smart-direction. The existing
:direction type simply asks for one of the four directions “left”,
“right”, “up” or “down”, without checking to see if there’s a frame in
that direction. Our new type, :smart-direction, will look around the
current frame, and only allow the user to choose a direction in which
another frame lies. If only one direction is possible it will return
that automatically without troubling the user. It signals an error an
error for invalid directions; it could alternately return a “nil”
value in those cases, and let the command handle that.
╭────
│ (define-stumpwm-type :smart-direction (input prompt)
│ (let ((valid-dirs
│ (loop ; gather all the directions in which there's a neighbouring
frame
│ with values = '(("up" :up)
│ ("down" :down)
│ ("left" :left)
│ ("right" :right))
│ with frame-set =
│ (group-frames (window-group (current-window)))
│ for dir in values
│ for neighbour = (neighbour
│ (second dir)
│ (window-frame (current-window)) frame-set)
│ if (and neighbour (frame-window neighbour))
│ collect dir))
│ (arg (argument-pop input))) ; store a possible argument
│ (cond ((null valid-dirs) ; no directions, bail out
│ (throw 'error "No valid directions"))
│ (arg ; an arg was bound, but is it valid?
│ (or (second (assoc arg valid-dirs :test #'string=))
│ (throw 'error "Not a valid direction")))
│ ((= 1 (length valid-dirs)) ; only one valid direction
│ (second (car valid-dirs)))
│ (t ; multiple possibilities, prompt for direction
│ (second (assoc (completing-read input prompt valid-dirs
│ :require-match t)
│ valid-dirs :test #'string=))))))
│
│ (defcommand smarty (dir) ((:smart-direction "Pick a direction: "))
│ ;; `dir' is a keyword here
│ (message "You're going ~a" (string-downcase dir)))
│
│ (define-key *root-map* (kbd "R") "smarty right")
╰────
- [STUMP] manual addition part 2: commands and types,
Eric Abrahamsen <=