stumpwm-devel
[Top][All Lists]
Advanced

[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")
  ╰────




reply via email to

[Prev in Thread] Current Thread [Next in Thread]