### 3.3 StumpWM Types ¶

All command arguments must be of a defined “StumpWM type”. The following types are pre-defined:

: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 yet 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 it hasn’t, 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 word or a double quote delimited string argument from the argument line. Backslashes may be used to escape double quotes or backslashes inside double quoted strings.
- `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 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")

Macro: define-stumpwm-type type(input prompt) &body body ¶

  Create a new type that can be used for command arguments. type can be any symbol.

  When body is evaluated input is bound to the argument-line. It is passed to `argument-pop`, `argument-pop-rest`, etc. prompt is the prompt that should be used when prompting the user for the argument.

      (define-stumpwm-type :symbol (input prompt)
       (or (find-symbol
             (string-upcase
               (or (argument-pop input)
                   ;; Whitespace messes up find-symbol.
                   (string-trim " "
                                (completing-read (current-screen)
                                                 prompt
                                                 ;; find all symbols in the
                                                 ;;  stumpwm package.
                                                 (let (acc)
                                                   (do-symbols (s (find-package "STUMPWM"))
                                                     (push (string-downcase (symbol-name s)) acc))
                                                   acc)))
                   (throw 'error "Abort.")))
             "STUMPWM")
           (throw 'error "Symbol not in STUMPWM package")))

      (defcommand "symbol" (sym) ((:symbol "Pick a symbol: "))
        (message "~a" (with-output-to-string (s)
                          (describe sym s))))

  This code creates a new type called `:symbol` which finds the symbol in the stumpwm package. The command `symbol` uses it and then describes the symbol.