[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3. Commands

If you’ve used Emacs before you’ll find the distinction between commands and functions familiar. Commands are simply functions that can be bound to keys and executed interactively from StumpWM’s input bar. Whereas, in Emacs, the special "(interactive)" declaration is used to turn a function into a command, in StumpWM commands are made with a separate defcommand or define-interactive-keymap macro.

Once a command is defined, you can call it by invoking the colon command (C-t ;), and typing the name of the command. This may be sufficient for commands that aren’t used very often. To see all the currently-defined commands, invoke the command called commands: ie press C-t ;, type “commands”, and hit return.

Commonly-used commands can also be bound to a keystroke, which is much more convenient. To do this, use the define-key function (see Key Bindings), giving the name of the command as a string. For example:

 
(define-key *root-map* (kbd "d") "exchange-direction")

You cannot give the command name as a symbol, nor can you bind a key to a regular function defined with defun.

If the command takes arguments (see Writing Commands), you can fix those arguments when defining the key-binding, by including the arguments in the same string as the command name, separated by a space. For instance, the exchange-direction command, which is unbound by default, requires a direction in which to exchange windows. If you call exchange-direction directly, it will prompt you for the direction. If you know that you often exchange in left/right directions, and want those actions bound to keys, you can use the following in your customization file:

 
(define-key *root-map* (kbd "[") "exchange-direction left")
(define-key *root-map* (kbd "]") "exchange-direction right")

Multiple arguments can be included by adding them to the command string, separated by spaces. Not all argument types can be represented as strings, but StumpWM will do its best to convert types.

StumpWM does not implement the Emacs concept of prefix arguments.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.1 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 "~a, in six years you 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 key-binding, 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.

It is possible to limit the scope under which the command will be usable: a command can be defined to work only in tile groups, or only in floating groups (the only two types of groups that currently exist). This is done by replacing the name of the command with a two-element list: the name of the command as a symbol, and either the symbol tile-group or floating-group. For instance, the next command, which only functions in tile groups, is defined this way:

 
(defcommand (next tile-group) …)

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

3.2 Interactive Keymaps

Interactive keymaps are a special type of command that basically pushes another keymap on top of the current one. The new keymap will only be removed after an exit command is run. An example is iresize.

The macro define-interactive-keymap is used to define an interactive keymap. The first argument is the same as defcommand. The second argument is a list of extra configurations that can be used for controlling the command and the rest are the key bindings for the new command.

For instance, a simple interactive keymap:

 
(define-interactive-keymap my-new-command nil
  ((kbd "a") "execute-a-command")
  ((kbd "b") "execute-b-command"))

This creates a command called my-new-command that, when called, will activate the interactive keymap mode. In this mode, the user can press “a” or “b” repeatedly, omitting any prefix. The default exit commands are RET, C-g and ESC.

The available configuration is on-enter, on-exit and abort-if:

 
(defun before-foo () (message "start foo"))
(defun after-foo () (message "end foo"))
(defun foo-p () (and *bar* *baz*))
(defparameter *custom-exit-keys* '((kbd "RET") (kbd "SPC")
                                   (kbd "C-g") (kbd "ESC")))

(define-interactive-keymap foo (:on-enter #'before-foo
                                :on-exit #'after-foo
                                :abort-if #'foo-p
                                :exit-on *custom-exit-keys*))

In the above example, the message “start foo” will appear before starting the interactive keymap, “end foo” will appear right after the command exits; We’ve added SPC as an exit key with custom exit keys. Also, the command executes only if the variables *bar* and *baz* are true.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

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”:

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")

[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Raimon Grau on May 28, 2019 using texi2html 1.82.