;; -*- sawfish -*- ;; waffle.jl v1.4 -- switch to or exec new task ;; Time-stamp: <2002/06/01 16:03:24 sfarrell> ;; ;; Copyright (C) 2000, 2001 Stephen Farrell ;; ;; This file is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 2, or (at your option) ;; any later version. ;;; Commentary: ;; Typically graphical environments separate the tasks of switching ;; between applications and starting new ones. I'm not sure why this ;; is, since the thought process is interwoven: I only want to start a ;; program if it isn't currently running or the running windows are ;; unsuitable; I only want to switch to a program if it is currently ;; running and one of the running windows is suitable. Really, I know ;; what I want to do and don't want to take the time to grok the state ;; of my desktop to do it. ;; ;; Waffle let's you type in what it is you want, interactively see if ;; it is already running, and only then decide whether to switch to a ;; window or launch a new program. Say, for example, that I want a ;; terminal to run a command. With waffle, I can type in "term", get ;; a list of terminals which are running, and then decide to reuse an ;; existing terminal (maybe it already has the right path and history) ;; or start up a new one. ;; ;; Waffle works by essentially combining "iswitch" (interactive ;; switching between windows, inspired by the emacs mode by Stephen ;; Eglen) and the standard "run" type utility which launches ;; application by name. As you type, the run history and open window ;; list are filtered by case-insensitive substring search. So say you ;; start to type "netscape", by the time you get to "nets" you will ;; see a listing of the time you launched netscape on top, and you ;; will see a listing of all the netscape windows on the bottom. The ;; lists will be narrowed based on the what you have typed. To ;; resolve ambiguities, press C-s to switch to a currently running ;; window, and C-e to exec a new one. ;; ;; To use: ;; 1. put waffle.jl and string2.jl in your load-path ;; 2. add this code to your ~/.sawfishrc ;; (require 'waffle) ;; (waffle-initialize) ;; 3. Middle-click/Customize/Bindings/Add/Waffle (recommend C-A-TAB) ;;; Thanks: ;; Topi Paavola ;; -- wrote iswitch-window.jl, from which some ideas and code were borrowed ;; John Harper ;; -- for writing sawfish, and helping with a silly bug ;; Christian Gromoll ;; -- suggestions and testing ;;; TODO ;; ;; - Better job of getting program list (not just history!) ;; - use kde/gnome functions? ;; - there are some bugs causing errors like: ;; *** Bad argument: #, -5, 3 (require 'string2) (require 'window-order) (defvar waffle-elision-str "...") (defvar waffle-max-len 70) (defvar waffle-verbatim nil) (defvar waffle-filler nil) (defvar waffle-history-file-name "~/.sawfish-waffle-history") (defvar waffle-history-file nil) (defvar waffle-max-height 0) (defun waffle-initialize () (setq waffle-history-file (expand-file-name waffle-history-file-name)) (setq waffle-history (waffle-load-history)) (setq waffle-verbatim (make-string waffle-max-len 32)) ;XXX (setq waffle-max-height (- (quotient (screen-height) (font-height default-font)) 20)) (setq waffle-filler (concat (make-string (quotient waffle-max-len 2) 32) "-- waffle --" (make-string (quotient waffle-max-len 2) 32)))) (defun waffle-verbatim-set (str) ;; so this is something of hack ;(message (concat "verbatim: " waffle-verbatim)) (let ((i 0)) (while (< i waffle-max-len) (aset waffle-verbatim i 32) (setq i (+ i 1)))) (when (stringp str) (let ((i 0) (len (length str))) (while (< i len) (aset waffle-verbatim i (aref str i)) (setq i (+ i 1)))))) (defun waffle-load-history () (let ((file-name waffle-history-file)) (if (file-exists-p file-name) (let ((file (open-file file-name 'read)) (reading t) (history nil)) (while reading (let ((line (read-line file))) (if line (setq history (cons (string2-replace line "\n" "") history)) (setq reading nil)))) (reverse history)) nil))) (defun waffle-save-history (history) (let ((file (open-file waffle-history-file 'write))) (while history (write file (concat (string2-trim (car history)) "\n")) (setq history (cdr history))))) (defun waffle-sublist (ls len rev) (if (> (length ls) len) (let* ((new nil) (beg (if rev 0 (- (length ls) len))) (end (if rev len (length ls))) (i beg)) (while (< i end) (setq new (cons (nth i ls) new) i (+ 1 i))) (reverse new)) ls)) (defun waffle-sublist-with-elision (ls len rev) (let* ((excess (if (> (length ls) len) t nil)) (a (waffle-sublist ls (if excess (- len 1) len) rev)) (b (when excess (list waffle-elision-str)))) (if rev (append a b) (append b a)))) (defun waffle-mode-list (ls m mode explicit-mode) (let* ((height (quotient waffle-max-height 2)) (mode-list (if (and explicit-mode (not (eq explicit-mode m))) (list waffle-elision-str) (waffle-sublist-with-elision ls height (eq m 'switch)))) (filler (when (> height (length mode-list)) (make-list (- height (length mode-list)) "")))) (mapcar (lambda (x) (string2-elipsis x waffle-max-len)) (if (eq m 'switch) (append mode-list filler) (append filler mode-list))))) (defun waffle-display (execs windows mode explicit-mode input) (string2-join (append (list waffle-filler) (waffle-mode-list (mapcar (lambda (x) (car x)) execs) 'exec mode explicit-mode) (list "") (list (concat (let ((prompt (if (eq mode 'exec) "exec new" "switch to"))) (if explicit-mode prompt (concat "(" prompt ")"))) " " input "_ [" (string2-elipsis (if (eq mode 'switch) (caar windows) (string2-trim (car (last execs)))) (- waffle-max-len (length input) 5)) "]")) (list "") (waffle-mode-list (mapcar (lambda (x) (car x)) windows) 'switch mode explicit-mode) (list "")) "\n")) (defun waffle-execs () (mapcar (lambda (x) (cons x nil)) (cons waffle-verbatim (delq waffle-verbatim waffle-history)))) (defun waffle-windows () (let ((windows (mapcar (lambda (w) (cons (concat (when (window-workspaces w) (concat (number->string (car (window-workspaces w))) " / ")) (when (window-iconified-p w) "(") (window-class w) " / " (window-name w) (when (window-iconified-p w) ")") ) (window-id w))) (window-order nil t t)))) (if (> (length windows) 1) (append (list (nth 1 windows) (nth 0 windows)) (cddr windows)) nil))) (defun waffle-rotate (ls0 clockwise) (let* ((ls (if clockwise ls0 (reverse ls0))) (rot (append (cdr ls) (list (car ls))))) (if clockwise rot (reverse rot)))) (defun waffle-read-event () (throw 'waffle-read (event-name (current-event)))) (defun waffle-ci-filter (input ls explicit-mode) (cond ((= (length input) 0) ls) (ls (filter (lambda (x) (string2-contains (string-downcase (car x)) (string-downcase input))) ls)) (t nil))) (defun waffle-dialog () "interactive dialog to select program to run" (when (grab-keyboard) (unwind-protect (let* ((input "") (last-input "") (key "") (mode nil) (explicit-mode nil) (execs-all (waffle-execs)) (windows-all (waffle-windows)) (execs execs-all) (windows windows-all)) (add-hook 'unbound-key-hook waffle-read-event) (catch 'exit (while t (unless (eq last-input input) (setq execs (waffle-ci-filter input execs explicit-mode)) (setq windows (waffle-ci-filter input windows explicit-mode))) (setq mode (cond (explicit-mode explicit-mode) (windows 'switch) (t 'exec))) (setq last-input input) (display-message (waffle-display execs windows mode explicit-mode input)) (setq key (catch 'waffle-read (recursive-edit))) (cond ((or (equal key "C-g") (equal key "A-g") (equal key "ESC")) (throw 'exit nil)) ((equal key "TAB") (let ((completed (string2-max-common-substring input (mapcar (lambda (x) (car x)) (if (eq mode 'exec) (filter (lambda (x) (not (eq (car x) waffle-verbatim))) execs) windows)) t))) (if (equal completed input) (if (eq mode 'exec) (setq execs (waffle-rotate execs nil)) (setq windows (waffle-rotate windows t))) (setq input completed)))) ((equal key "C-s") (when (or (not execs) (eq explicit-mode 'switch)) (setq windows (waffle-rotate windows t))) (setq explicit-mode 'switch)) ((equal key "C-e") (when (or (not windows) (eq explicit-mode 'exec)) (setq execs (waffle-rotate execs nil))) (setq explicit-mode 'exec)) ((equal key "C-a") (setq explicit-mode nil)) ((equal key "C-u") ; delete line (setq execs execs-all) (setq windows windows-all) (setq input nil)) ((equal key "BS") ; backspace (setq execs execs-all) (setq windows windows-all) (when (> (length input) 0) (setq input (substring input 0 (1- (length input)))))) ((equal key "RET") ; run it (throw 'exit (cons mode (if (eq mode 'exec) (or (car (reverse execs)) input) (car windows))))) ((equal key "SPC") ; hack to get space to work (setq input (concat input " "))) ((= 1 (length key)) ; normal key stroke (setq input (concat input key)))) (waffle-verbatim-set input)))) (remove-hook 'unbound-key-hook waffle-read-event) (display-message nil) (ungrab-keyboard)))) (defun waffle-display-message (message) (let ((read-event (lambda () (throw 'read (event-name (current-event)))))) (when (grab-keyboard) (unwind-protect (catch 'exit (add-hook 'unbound-key-hook read-event) (display-message message) (catch 'read (recursive-edit))) (remove-hook 'unbound-key-hook read-event) (display-message nil) (ungrab-keyboard))))) (defun waffle () "Unified window launcher/switcher" (interactive) (waffle-verbatim-set nil) (let* ((in (waffle-dialog)) (mode (car in)) (str0 (cadr in)) (data (cddr in))) (when (and (stringp str0) (> (length str0) 0)) (let ((str (substring str0 0))) ; a copy (if (eq mode 'switch) (display-window (get-window-by-id data)) (if (= 0 (system (concat "which " (car (string2-split (string2-trim str) " ")) " >/dev/null 2>&1"))) (progn (system (concat str "&")) (setq waffle-history (if (filter (lambda (x) (equal x str)) waffle-history) (append (filter (lambda (x) (not (equal x str))) waffle-history) (list str)) (append waffle-history (list str)))) (waffle-save-history waffle-history)) (waffle-display-message "Whatchew talkin' 'bout Willis?"))))))) (provide 'waffle)