;; -*- emacs-lisp -*- ;;; emacs-homebrew.el --- small custom emacs functions. ;; Author: Mark Triggs ;; Keywords: lisp ;; $Id: emacs-homebrew.el,v 1.295 2006/07/09 23:46:48 mst Exp $ ;; 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. ;; This file is distributed in the hope that it will be useful, but ;; WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;; General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to the Free ;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA ;; 02111-1307, USA. ;;; Code: (require 'cl) (defun mail-use-gnus (&optional address &rest args) "Write a mail to `address' using gnus" (interactive) (gnus-no-server) (gnus-msg-mail) (when address (insert address))) (defun fill-this-line () "Fill the current line only" (interactive) (save-excursion (beginning-of-line) (set-mark-command nil) (end-of-line) (fill-region (region-beginning) (region-end)))) ;; (global-set-key (kbd "C-x k") 'de-context-kill) ;; (defun de-context-kill (arg) ;; "Kill buffer, taking gnuclient into account." ;; (interactive "p") ;; (when (and (buffer-modified-p) ;; (eq last-command 'de-context-kill) ;; (not (string-match "\\*.*\\*" (buffer-name))) ;; (= 1 arg)) ;; (diff-buffer-with-associated-file) ;; (error "Buffer has unsaved changes")) ;; (if (and (boundp 'gnuserv-minor-mode) ;; gnuserv-minor-mode) ;; (gnuserv-edit) ;; (set-buffer-modified-p nil) ;; (kill-buffer (current-buffer)))) (defun iswitchb-mst-files-to-start () (let* ((file-buffers (mapcan (lambda (buffer) (if (buffer-file-name buffer) (list buffer) nil)) (buffer-list))) (nonfile-buffers (remove-if-not (lambda (b) (member b file-buffers)) (buffer-list)))) ;; Move buffers who don't have files associated with them to the end. (iswitchb-to-end (mapcar 'buffer-name nonfile-buffers)))) (defvar iswitchb-mst-to-end-regexp nil "Regular expression matching buffers to be moved to the end of the iswitchb list") (defun iswitchb-mst-summaries-to-end () "Move the summaries to the end of the list. Ripped and modified from the iswitchb docs" (let* ((case-fold-search t) (summaries (delq nil (mapcar (lambda (x) (if (and x (string-match iswitchb-mst-to-end-regexp x)) x nil)) iswitchb-temp-buflist)))) (iswitchb-to-end summaries))) ;; ;; open a file as root ;; (defun find-file-root (file) ;; (interactive "fOpen file (as root): ") ;; (require 'tramp) ;; (find-file (concat "/[multi/sudo:root@localhost]" file))) ;; (global-set-key [(control x) (control r)] 'find-file-root) (defun weigh-in () (interactive) (let ((weight (read-string "Weight: "))) (with-current-buffer (find-file-noselect "~/.weight_log") (goto-char (point-max)) (insert (format "%s\t%s" (format-time-string "%m-%d-%y") weight)) (save-buffer) (kill-buffer nil))) (when (y-or-n-p "Run weightcheck? ") (with-frame-display (shell-command "~/.bin/weightcheck")))) (defun irc () "Load and start erc" (interactive) (when (try-require 'erc 'mst-erc) (erc-mst-select))) (defvar gnus-mst-frame nil "The frame gnus is running in") (defun mst-start-gnus () (interactive) (shell-command "sawfish-client -e '(select-workspace 2)'") (gnus-other-frame 2)) (defun average (&rest numbers) (string-to-number (format "%.4f" (typecase (car numbers) (cons (/ (reduce '+ (car numbers)) (float (length (car numbers))))) (t (/ (reduce '+ numbers) (float (length numbers)))))))) (defun mst-wrap-string (s &optional fill prefix) (with-temp-buffer (insert s) (let ((fill-column (or fill fill-column)) (fill-prefix prefix) (filladapt-mode nil)) (fill-region (point-min) (point-max))) (buffer-string))) (defun mst-add-template (regex &optional front end) "Add a template for the file extension matching 'regex', and include an front line at the top of the file and an end line at the bottom" (setq auto-insert-alist (cons `((,regex) . (lambda () ,(when front `(insert (format "%s\n" ,(eval front)))) (narrow-to-region (point) (point)) (insert "\n") (unwind-protect (let ((short-description (read-string "One-line description: ")) (long-description (read-string "Long Description: "))) (when (and short-description (not (string= short-description ""))) (insert (format "%s\n\n" short-description))) (when (and long-description (not (string= long-description ""))) (insert (mst-wrap-string (format "Description: %s" long-description) (- fill-column (length comment-start) 2 ; for spaces (length comment-end)) " ")) (newline 2)) (insert (format "Author: %s <%s>\n\n" (user-full-name) user-mail-address)) (let ((comment-empty-lines t)) (comment-region (point-min) (point-max)) (when (string= comment-end "") (replace-regexp "^$" comment-start nil (point-min) (point-max) ))) (newline 3) (save-excursion (replace-regexp " +$" "" nil (point-min) (point-max)))) (widen)) ,(when end `(insert (format "%s\n\n" ,(eval end)))))) auto-insert-alist))) (defun prompt-for (type prompt) (let ((input (read-from-minibuffer (format "%s: " prompt)))) (cond ((typep input type) input) ((typep (car (ignore-errors (read-from-string input))) type) (car (read-from-string input))) (t nil)))) (defun mst-set-buffer-indentation () (interactive) (let ((use-tabs (y-or-n-p "Use hard tabs? "))) (cond (use-tabs (setq indent-tabs-mode t) (set (tab-controller major-mode) 8)) (t (setq indent-tabs-mode nil) (set (tab-controller major-mode) (or (prompt-for 'integer "Indentation width? ") 4))))) (set (make-local-variable 'parens-require-spaces) (y-or-n-p "Spaces before parens? "))) (defun mst-invent-word () "Add the word at the point to the personal dictionary" (interactive) (let ((word (word-at-point))) (with-current-buffer (find-file-noselect "~/.aspell.english.pws") (end-of-buffer) (insert (concat word "\n")) (save-buffer) (kill-buffer nil)) (ispell-pdict-save t t) (flyspell-unhighlight-at (point)))) (defun tab-controller (mode) "Return the variable that sets the indentation width for MODE. This allows (mst-code-settings) to provide a consistent way of specifying the width of indentation that should be used" (case mode ((python-mode) 'py-indent-offset) ((c-mode c++-mode java-mode php-mode) 'c-basic-offset) ((sh-mode) 'sh-basic-offset) ((cperl-mode) (make-local-variable 'cperl-indent-level)) ((ruby-mode) 'ruby-indent-level) (t (make-local-variable 'dummy)))) (defun show-code-settings-in-modeline () (interactive) (let ((map (make-sparse-keymap))) (define-key map [mode-line mouse-1] (lambda () (interactive) (let ((use-dialog-box nil)) (mst-set-buffer-indentation)))) (list ':propertize (format " (%s)" (reduce (lambda (s1 s2) (if s1 (concat s1 s2) s2)) (list (if (my-code-p) "m" nil) (if indent-tabs-mode "tabs" (format "i%d" (symbol-value (tab-controller major-mode))))))) 'keymap map 'help-echo "Mouse-1: Set indentation mode for this buffer"))) (defvar mst-code-settings-map (make-sparse-keymap)) (define-key mst-code-settings-map (kbd "C-C C-t C-t") 'run-unit-tests) (define-key mst-code-settings-map (kbd "C-C e t") 'run-unit-tests) (define-key mst-code-settings-map (kbd "C-C e s") 'set-unit-test-command) (define-key mst-code-settings-map (kbd "C-c e e") 'open-unit-test-file) (define-key mst-code-settings-map (kbd "C-c s") 'ispell-comments-and-strings) (define-key mst-code-settings-map (kbd "C-c S") 'toggle-flyspell-code) ;; (define-key mst-code-settings-map (kbd "(") 'insert-parentheses) ;; (define-key mst-code-settings-map (kbd ")") 'move-past-close-and-reindent) (defun toggle-flyspell-code () (interactive) (when (not (boundp 'flyspell-status)) (make-variable-buffer-local 'flyspell-status)) (cond (flyspell-status (setq flyspell-status nil) (flyspell-mode-off)) (t (setq flyspell-status (not flyspell-status)) (flyspell-prog-mode)))) (defvar mst-tabs-here nil) (define-key mst-code-settings-map (kbd "RET") 'newline-and-indent) (define-minor-mode mst-code-settings-mode "Settings I use when coding" nil " mst" mst-code-settings-map (make-variable-buffer-local 'mst-tabs-here) ;; default settings ;; ;; (glasses-mode) (highlight-fixmes-mode 1) (condition-case err (hs-minor-mode 1) (error () (message "Note: %s" (error-message-string err)))) (filladapt-mode -1) (setq parens-require-spaces t) (setq show-trailing-whitespace t) (setq tab-width 8) (labels ((fix-tabs () (cond ((equal mst-tabs-here 'fix) (untabify (point-min) (point-max))) ((equal mst-tabs-here 'leave) nil) ((buffer-has-tabs-p) (cond ((y-or-n-p "Tabs found. Untabify buffer? ") (untabify (point-min) (point-max)) (when (y-or-n-p "Always untabify this buffer? ") (setq mst-tabs-here 'fix))) ((y-or-n-p "Leave tabs alone in future? ") (setq mst-tabs-here 'leave) ;; use tabs to remain consistent with stupidity. (setq tab-width 8) (setq indent-tabs-mode t))))))) (cond ((my-code-p) ;; fiddle with the code to make it more consistent (fix-tabs)) (t ;; try to adjust settings to match the style of the buffer (when (grok-buffer-settings) (multiple-value-bind (use-tabs width) (grok-buffer-settings) (setq indent-tabs-mode use-tabs) (set (tab-controller major-mode) width)))))) (unless (member '(:eval (show-code-settings-in-modeline)) mode-line-format) (setq mode-line-format (append (butlast mode-line-format) '((:eval (show-code-settings-in-modeline))) (last mode-line-format)))) (setq fill-column 79) (highlight-long-lines-mode 1)) (defun mst-code-settings (&optional indent-width tabs) "Set default settings that I use for most programming modes" (setq indent-tabs-mode tabs) (set (tab-controller major-mode) (or indent-width 4)) (mst-code-settings-mode 1)) (defun Footnote-reset () "Reset footnotes by brute force" (interactive) (setq footnote-text-marker-alist nil)) ;; The following defun is taken almost directly from erc.el. It's a great idea, ;; and I like to have it available elsewhere. (defun popup-input-buffer () "Provide a input buffer." (interactive) (let ((buffer-name (generate-new-buffer-name "*input*")) (mode (intern (completing-read "Mode: " (mapcar (lambda (e) (list (symbol-name e))) (apropos-internal "-mode$" 'commandp)) nil t)))) (pop-to-buffer (make-indirect-buffer (current-buffer) buffer-name)) (narrow-to-region (point) (point)) (funcall mode) (let ((map (copy-keymap (current-local-map)))) (define-key map (kbd "C-c C-c") (lambda () (interactive) (kill-buffer nil) (delete-window))) (use-local-map map)) (shrink-window-if-larger-than-buffer))) (defun group-list (lst num) "Group 'lst' into sublists of length 'num'" (cond ((null lst) nil) ((null (nthcdr num lst)) (list lst)) (t (cons (subseq lst 0 num) (group-list (nthcdr num lst) num))))) (defmacro screen-jump (screen) "A function to jump to some screen" `(lambda () (interactive) (escreen-goto-screen ,(eval screen)))) (defun mst-make-screen (key) "Make a new screen, and bind a key to select it" (interactive "kKey to bind:") (escreen-create-screen) (global-set-key key (screen-jump escreen-current-screen-number))) ;; this might explode (defun mst-configure-screen () (interactive) (let ((configuration (list (cons 0 "*scratch*") (cons gnus-screen "*Group*") (cons erc-screen (buffer-name (car (erc-buffer-list))))))) (loop (mapc (lambda (pair) (destructuring-bind (screen . buffer) pair (escreen-goto-screen screen) (force-switch-buffer buffer))) configuration) ;; check that it worked, and try again if it didn't (when (dolist (pair configuration t) (destructuring-bind (screen . buffer) pair (escreen-goto-screen screen) (unless (string= (buffer-name (current-buffer)) buffer) (return nil)))) (return t))))) (defun bind-key (key form) "Make a new screen, and bind a key to select it" (interactive "kKey to bind: xForm: ") (define-key global-map key `(lambda () (interactive) ,form))) (defun show-morse (start end) "Show the decoded version of some morse code in the minibuffer" (interactive "r") (narrow-to-region start end) (let* ((morse (buffer-string)) (text (with-temp-buffer (insert morse) (unmorse-region (point-min) (point-max)) (buffer-string)))) (widen) (message text))) (defun visible-buffers () (let ((buffers '())) (walk-windows (lambda (w) (push (window-buffer w) buffers))) buffers)) (defun force-switch-buffer (buffer) "Switch to a buffer (overcomes a problem with gnuserv and escreens)" (interactive "bBuffer name: ") (list-buffers) (other-window 1) (goto-char (point-min)) (search-forward buffer nil nil nil) (Buffer-menu-this-window) (delete-other-windows) (kill-buffer "*Buffer List*")) (defun group-by (list key) "Group LIST by items whose KEY values are equal" (let ((groups (make-hash-table :test 'equal))) (mapc (lambda (elt) (push elt (gethash (funcall key elt) groups))) list) (let ((acc '())) (maphash (lambda (k v) (push (cons k v) acc)) groups) acc))) (defun buffer-mode (buffer) "Return the major mode of BUFFER" (save-excursion (set-buffer buffer) major-mode)) (defun buffer-lines () "return the number of lines in the current buffer" (save-excursion (goto-char (point-min)) (loop while (progn (end-of-line) (not (eobp))) count t do (next-line 1)))) (defun perl-wrap-string (start finish) "Wrap a perl string by making it into a bunch of concatenated strings" (interactive "r") (require 'perl-mode) (goto-char start) (loop do (forward-char) while (not (looking-at "\""))) (forward-char) (loop with last-space = nil with last-char = nil do (cond ((> (current-column) (- fill-column 5)) (goto-char last-space) (insert "\" .\"") (backward-char 1) (newline) (perl-indent-command)) ((looking-at "\n") (delete-char 1)) ((looking-at " ") (setq last-space (point))) (t nil)) (forward-char) while (or (not (looking-at "\"")) (string= last-char "\\")))) (defun perl-unwrap-string (start finish) "Wrap a perl string by making it into a bunch of concatenated strings" (interactive "r") (let ((string-start start) (string-finish finish)) (goto-char string-start) (while (not (looking-at "\"")) (incf string-start) (forward-char)) (goto-char string-finish) (while (not (looking-at "\"")) (decf string-finish) (backward-char)) (replace-regexp "\\(\" \\.\\|^ *\"\\|\n\\)" "" nil string-start string-finish))) ;; more fun but overflows sometimes. ;; (defun do-once (hook fn) ;; (let ((hook-fn (list nil))) ;; (setcar hook-fn 'lambda) ;; (setcdr hook-fn `((&rest args) ;; (apply ',fn args) ;; (remove-hook ',hook ,hook-fn))) ;; (add-hook hook hook-fn))) (defun do-once (hook fn) "Add FN to hook and set it to be removed" (let ((hook-fn (gensym))) (setf (symbol-function hook-fn) `(lambda (&rest args) (unwind-protect (apply ,fn args) (remove-hook ',hook ',hook-fn)))) (add-hook hook hook-fn))) (defun noweb-view () (interactive) (shell-command-to-string (format "noweave -delay -x %s > %s.tex" (buffer-file-name) (file-name-sans-extension (buffer-file-name)))) (shell-command-to-string (format "latex %s.tex" (file-name-sans-extension (buffer-file-name)))) (shell-command-to-string (format "latex %s.tex" (file-name-sans-extension (buffer-file-name)))) (start-process "xdvi" nil "xdvi" (format "%s.dvi" (file-name-sans-extension (buffer-file-name))))) ;; for (java-class-lookup) (defvar java-api-base nil "The base directory of the javadoc API") (defvar java-api-index nil "Path to a file containing a bunch of javadoc HTML files relative to java-api-base") (defvar *java-index* nil) (defun java-class-read-index (&optional force) (when (or (not *java-index*) force) (setq *java-index* nil) (with-temp-buffer (insert-file java-api-index) (goto-char (point-min)) (while (not (eobp)) (let ((path (buffer-substring (line-beginning-position) (line-end-position)))) (push (cons (replace-regexp-in-string "/" "." (file-name-sans-extension path)) path) *java-index*)) (forward-line 1)))) *java-index*) (defun completing-read-isearch (table callback) (with-current-buffer (get-buffer-create " *isearch completions*") (erase-buffer) (dolist (elt table) (insert (propertize (concat (car elt) "\n") 'dest (cdr elt)))) (goto-char (point-min)) (let ((map (make-sparse-keymap))) (define-key map (kbd "RET") `(lambda () (interactive) (let ((target (get-text-property (point) 'dest))) (delete-window) (funcall ,callback target)))) (define-key map (kbd "q") 'bury-buffer) (use-local-map map))) (pop-to-buffer (get-buffer-create " *isearch completions*"))) (defun java-class-lookup () (interactive) (completing-read-isearch (java-class-read-index) (lambda (doc) (w3m-with-saved-window-configuration (format "file://%s/%s" (expand-file-name java-api-base) doc) nil)))) (defun my-string-to-number (s) "Convert a string to a number. Returns NIL if this is not possible." (let ((n (condition-case () (destructuring-bind (r . i) (read-from-string s) (if (and r (= (length s) i)) r nil)) (invalid-read-syntax () nil)))) (if (numberp n) n nil))) (defmacro with-working-directory (dir &rest body) (let ((old (gensym))) `(let ((,old default-directory)) (cd ,dir) (prog1 (progn ,@body) (cd ,old))))) (put 'with-working-directory 'lisp-indent-function 1) (defvar my-lisps '(("cmu" . "*cmulisp*") ("clisp" . "*clisp-hs*") ("shell" . "*clisp-shell*"))) (defun lispshell () (interactive) (ignore-errors (kill-buffer "*ilisp-send*")) (clisp-hs "clisp-shell" "/usr/bin/clisp -M /home/mst/.bin/shell.mem -q -ansi")) (defun set-lisp () "A quick hack to make it easier to jump between different lisps" (interactive) (when (eq major-mode 'lisp-mode) (setq ilisp-buffer (cdr (assoc (completing-read "Use dialect: " my-lisps) my-lisps))))) (defun number-lines-region (start end &optional fmt) "Prepend a line number to each line in the current region. If a prefix arg is used, number blank lines too." (interactive "r\nsFormat string (default \"%%d.\"): ") (let* ((fmt (if (string= fmt "") "%d." fmt )) (lines (count-lines start end)) (width (length (format fmt lines)))) (save-excursion (goto-char start) (let ((ctr 0)) (dotimes (i lines) (beginning-of-line) (when (or current-prefix-arg (not (looking-at "^$"))) (insert (format (concat "%-" (number-to-string width) "s ") (format fmt (incf ctr))))) (next-line 1)))))) (defun reselect-last-region () (interactive) (let ((start (mark t)) (end (point))) (goto-char start) (call-interactively' set-mark-command) (goto-char end))) (defun maybe-with-region (cmd) "Return a lambda that calls region command CMD on the current region, or on the last region if the mark is not active" `(lambda () (interactive) (if mark-active (call-interactively ',cmd) (funcall ',cmd (min (mark t) (point)) (max (mark t) (point)))))) (flet ((eql (&rest args) (apply 'string= args))) (eql "hi" "hi")) (defmacro string-case (form &rest clauses) `(flet ((eql (&rest args) (apply 'string= args))) (case ,form ,@clauses))) (put 'string-case 'lisp-indent-function 1) (defun eval-file () "Do whatever is necessary to run the code in the current file" (interactive) (string-case (file-name-extension (buffer-file-name)) ("c" (let ((tmp (make-temp-file "c"))) (shell-command (format "gcc -o %s %s" tmp (buffer-file-name)) "compile" "compile") (shell-command (format "%s; rm -f %s" tmp tmp)))) ("pl" (shell-command (format "chmod u+x %s" (buffer-file-name))) (shell-command (buffer-file-name))) ("hs" (shell-command (format "hugs %s" (buffer-file-name)))))) (defun eshell-with-command (command) (interactive "sCommand? ") (let ((eshell-buffer (eshell))) (with-current-buffer eshell-buffer (insert command) (eshell-send-input)) eshell-buffer)) ;; Hugs running stuff (defvar *hugs-buffer* nil) (defun hugs-run () (interactive) (split-window-vertically) (setq *hugs-buffer* (eshell-with-command "hugs"))) (defun hugs-load (&optional file) "Load FILE (or the current buffer-file) in the currently running hugs" (interactive) (let ((loadfile (or file (buffer-file-name)))) (with-current-buffer *hugs-buffer* (insert (format ":load %s" loadfile)) (eshell-send-input)))) (defun slime-lisp (cmdline) (interactive "sLisp command line?: ") (let ((inferior-lisp-program cmdline)) (call-interactively 'slime))) (defun remove-properties-from-string (s) (let ((s (copy-sequence s))) (set-text-properties 0 (length s) nil s) s)) (defun current-function-name () (save-excursion (backward-up-list) (forward-char 1) (remove-properties-from-string (thing-at-point 'symbol)))) ;; Just messing around.. (defmacro keyword-defun (&rest spec) (flet ((plist-to-let (plist) (cond ((null plist) nil) ((null (cdr plist)) (error "Invalid plist")) (t (cons (list (intern (subseq (symbol-name (car plist)) 1)) (cadr plist)) (plist-to-let (cddr plist))))))) (destructuring-bind (name args . body) spec (destructuring-bind (args keywords) (if (position '&key args) (list (subseq args 0 (position '&key args)) (subseq args (1+ (position '&key args)))) (list args '())) `(defun ,name (&rest spec1) (destructuring-bind ,args (subseq spec1 0 ,(length args)) (let ,keywords (map nil #'(lambda (pair) (set (car pair) (cadr pair))) (plist-to-let (subseq spec1 ,(length args)))) ,@body))))))) (defun trim-region (start end) (interactive "r") (replace-regexp " +$" "" nil start end) (replace-regexp "^ +" "" nil start end)) (defun diff-buffer-if-modified (&optional buffer) (when (and (buffer-file-name buffer) (buffer-modified-p buffer)) (set-buffer (or buffer (current-buffer))) (let ((tmp (make-temp-file "buffer-contents"))) (unwind-protect (progn (write-region (point-min) (point-max) tmp) (shell-command (format "diff -c %s %s" (buffer-file-name) tmp) "*Changes*")) (delete-file tmp))) (view-buffer-other-window "*Changes*") (other-window 1))) (defun dired-cwd-other-window () (interactive) (let ((dired-listing-switches "-laR")) (dired-other-window default-directory))) (defun view-file-other-temp-frame (&optional file) "Same as VIEW-FILE-OTHER-FRAME, but destroy the frame when it is exited" (interactive "fIn other frame view file: ") (let ((b (find-file-noselect file))) (with-current-buffer b (setq view-exit-action (lambda (buffer) (kill-buffer buffer) (delete-frame))) (view-mode)) (switch-to-buffer-other-frame b))) ;; An ugly hack. It works and that's all I'll say in its defence. (defun bison--type-action-block () (interactive) (indent-to-column bison-rule-semantic-action-column) (lexical-let* ((orig-buf (current-buffer)) (orig-pt nil) (temp-file (make-temp-file "action")) (offset (+ (current-column) c-basic-offset))) (insert "{\n") (setq orig-pt (point-marker)) (find-file-other-window temp-file) (c-mode) (setq fill-column (- fill-column offset)) (setq mode-line-format "Press C-c C-c when finished") (let ((map (copy-keymap (current-local-map)))) (define-key map (kbd "C-c C-c") (lambda () (interactive) (save-buffer) (let ((contents (mapcar #'(lambda (line) (if (string= line "") "\n" (concat (make-string offset 32) line "\n"))) (split-string (buffer-string) "\n")))) (kill-buffer-and-window) (switch-to-buffer orig-buf) (goto-char (marker-position orig-pt)) (mapc 'insert contents) (delete-file temp-file) (delete-blank-lines) (indent-to-column bison-rule-semantic-action-column) (insert "}")))) (use-local-map map)))) (defun count-words-region (beg end) (interactive "r") (save-excursion (narrow-to-region beg end) (unwind-protect (progn (goto-char (point-min)) (let ((count 0)) (while (and (not (eobp)) (forward-word 1)) (incf count)) (message "Region has %d words." count) (setq deactivate-mark t) count)) (widen)))) (defun count-words-document () "Count the number of words in the document part of the current LaTeX buffer." (interactive) (save-excursion (let ((contents (if mark-active (buffer-substring (region-beginning) (region-end)) (goto-char (point-min)) (buffer-substring (progn (search-forward "\\begin{document}" nil t) (match-end 0)) (progn (search-forward "\\end{document}" nil t) (match-beginning 0)))))) (with-temp-buffer (insert contents) (goto-char (point-min)) (delete-matching-lines "^%" (point-min) (point-max)) (goto-char (point-min)) (while (search-forward-regexp "^\\\\begin{\\(quot[^}]*\\)}" nil t) (let ((p (match-beginning 0))) (search-forward-regexp (format "^\\\\end{%s}" (match-string 1))) (delete-region p (point)))) (delete-matching-lines "^\\\\begin" (point-min) (point-max)) (delete-matching-lines "^\\\\end" (point-min) (point-max)) (count-words-region (point-min) (point-max)))))) (defmacro with-frame-display (&rest body) "Evaluate BODY with the DISPLAY environment variable set to the display the current frame is on." (let ((display (gensym))) `(let ((,display (getenv "DISPLAY"))) (setenv "DISPLAY" (frame-parameter (selected-frame) 'display)) ,@body (setenv "DISPLAY" ,display)))) (defun cycle-zippy () (interactive) (set-marker (mark-marker) (point) (current-buffer)) (insert (yow)) (let ((event nil)) (while (= (setq event (read-event)) ?z) (delete-region (mark t) (point)) (set-marker (mark-marker) (point) (current-buffer)) (insert (yow)) (message "Press z again to repeat")) (setq unread-command-events (list event)))) (defun current-line-number () "Return (narrowed) buffer line number at position POS. If POS is nil, use current buffer location." (let ((opoint (point)) start) (save-excursion (goto-char (point-min)) (setq start (point)) (goto-char opoint) (forward-line 0) (1+ (count-lines start (point)))))) (defun playlist () (interactive) (with-frame-display (start-process "playlist" nil "xterm" "-e" "~/.bin/playlist"))) (defun music () (interactive) (start-process "music" nil "music")) (defun wikipedia (&optional search) (interactive "sTopic?: ") (when (featurep 'escreen) (escreen-goto-screen w3m-screen)) (w3m-goto-url-new-session (format "http://en.wikipedia.org/wiki/Special:Search?search=%s" search))) (defun google (&optional search) (interactive "sSearch for?: ") (when (featurep 'escreen) (escreen-goto-screen w3m-screen)) (w3m-goto-url-new-session (format "http://google.com/search?q=%s" search))) (defun google-groups (&optional search) (interactive "sSearch for?: ") (when (featurep 'escreen) (escreen-goto-screen w3m-screen)) (w3m-goto-url-new-session (format "http://groups.google.com/groups?q=%s" search))) (defun chmod () (interactive) (shell-command (format "chmod %s %s" (read-from-minibuffer "Mode string?: " "u+x") (buffer-file-name)))) (defun reload-library () (interactive) (let ((old (symbol-function 'defvar))) (defmacro defvar (symbol &optional initvalue docstring) `(setq ,symbol ,initvalue)) (unwind-protect (call-interactively 'load-library) (setf (symbol-function 'defvar) old)))) (defun mst-button (text callback) (let ((map (make-sparse-keymap))) (dolist (key (list (kbd "RET") [mouse-1])) (define-key map key callback)) (let ((p (point))) (insert-button text 'keymap map 'face '(:background "gray90" :box (:line-width 2 :style released-button))) (set-text-properties p (point) '(rear-nonsticky t front-sticky t read-only t))))) (defmacro with-buffer-preserved (buffer-name &rest body) "Move BUFFER-NAME out of the way while executing BODY" (let ((tmp-name (gensym))) `(if (get-buffer ,buffer-name) (let ((,tmp-name (generate-new-buffer-name ,buffer-name))) (with-current-buffer (get-buffer ,buffer-name) (rename-buffer ,tmp-name)) (unwind-protect (progn ,@body) (kill-buffer ,buffer-name) (with-current-buffer ,tmp-name (rename-buffer ,buffer-name)))) (progn ,@body)))) (put 'with-buffer-preserved 'lisp-indent-function 1) (defun kill-buffer-show-diff () "Kill the current buffer, showing a diff if it has been modified." (interactive) (if (and (buffer-modified-p) (buffer-file-name) (not (string= (buffer-file-name) "")) (file-exists-p (buffer-file-name))) (save-window-excursion (with-buffer-preserved "*Diff*" (diff-buffer-with-file) (call-interactively 'kill-buffer))) (call-interactively 'kill-buffer))) (defun tex-to-text () (interactive) (let ((tex (current-buffer)) (text (get-buffer-create (format "%s text" (buffer-name (current-buffer)))))) (switch-to-buffer text) (text-mode) (insert-buffer-substring-no-properties tex) (goto-char (point-min)) (while (not (looking-at "^\\\\begin{document}")) (delete-region (point-at-bol) (1+ (point-at-eol)))) (delete-matching-lines "^\\\\\\(begin\\|end\\|newpage\\)") (save-excursion (let ((section 1) (subsection 0) (subsubsection 0)) (while (not (eobp)) (cond ((looking-at "\\\\section{") (save-excursion (replace-regexp "^.*{\\(.*\\)} *$" "\\1" nil (point) (point-at-eol))) (insert (format "%d. " section)) (setq section (1+ section) subsection 0 subsubsection 0)) ((looking-at "\\\\subsection{") (save-excursion (replace-regexp "^.*{\\(.*\\)} *$" "\\1" nil (point) (point-at-eol))) (insert (format "%d.%d. " section subsection)) (setq subsection (1+ subsection) subsubsection 0)) ((looking-at "\\\\subsubsection{") (save-excursion (replace-regexp "^.*{\\(.*\\)} *$" "\\1" nil (point) (point-at-eol))) (insert (format "%d.%d.%d. " section subsection subsubsection)) (setq subsubsection (1+ subsubsection))) (t nil)) (forward-line 1)))) (save-excursion (dolist (replacement '(("\\item" "*") ("\\$" "$") ("\\%" "%"))) (goto-char (point-min)) (apply 'replace-string replacement))) (save-excursion (while (re-search-forward "\\\\[^ {]+{\\([^}]+\\)}" nil t) (replace-match "\\1" nil nil) (goto-char (match-beginning 0)))) (save-excursion (replace-regexp "\[^{]+{\\([^}]+\\)}" "\\1")) (save-excursion (replace-regexp "\\(``\\|''\\)" "\"")))) (defun swap-buffer-names (buffer1 buffer2) (interactive "bBuffer 1: \nbBuffer 2: ") (let ((tmp-name (generate-new-buffer-name "temp")) (buffer1-name buffer1) (buffer2-name buffer2)) (with-current-buffer buffer1 (rename-buffer tmp-name)) (with-current-buffer buffer2 (rename-buffer buffer1-name)) (with-current-buffer tmp-name (rename-buffer buffer2-name)))) (defun fix-whitespace (beg end) (interactive "r") (save-excursion (replace-regexp "[[:blank:]]+$" "" nil beg end))) (defun fix-whitespace-buffer () (interactive) (fix-whitespace (point-min) (point-max)) (save-excursion (goto-char (point-max)) (delete-blank-lines))) ;; GUD hacks (defun gud-select-source-window () (interactive) (select-window (find-if (lambda (w) (eq (window-buffer w) gud-comint-buffer)) (window-list))) (other-window 2)) (defun gud-select-current-source () (interactive) (let ((w (selected-window))) (gud-select-source-window) (when (stringp (car gud-last-last-frame)) (switch-to-buffer (gud-find-file (car gud-last-last-frame)))) (select-window w))) ;; This is probably dangerous :o) (eval-after-load "gud" '(progn (defadvice gud-display-frame (after update-src-buffer activate) (when (eq gud-minor-mode 'gdba) (gud-select-current-source))))) ;; Beanshell comint stuff (defun bsh-start () (interactive) (when (and (get-buffer "*bsh*") (y-or-n-p "Kill existing *bsh*? ")) (kill-buffer "*bsh*")) (if (get-buffer "*bsh*") (switch-to-buffer-other-window "*bsh*") (switch-to-buffer-other-window (comint-run "bsh"))) (with-current-buffer "*bsh*" (set (make-local-variable 'comint-move-point-for-output) t) (let ((proc (get-buffer-process (current-buffer)))) (comint-send-string proc "show();") (comint-send-string proc "addClassPath(\".\");")))) (defun trim-trailing (s &rest strings) "From S, trim any trailing STRINGS" (with-temp-buffer (insert s) (goto-char (point-max)) (while (some (lambda (str) (looking-back str)) strings) (delete-backward-char 1)) (buffer-string))) (defun mst-region-string (beg end) (interactive "r") (trim-trailing (buffer-substring-no-properties beg end) "\n")) (defun bsh-send-command (s) (let ((proc (get-buffer-process "*bsh*"))) (with-current-buffer (process-buffer proc) (goto-char (point-max)) (insert s) (comint-send-input)))) (defun bsh-send-defun-or-line (&optional print-result) (interactive "P") (unless (get-buffer "*bsh*") (bsh-start)) (save-excursion (let ((command (let ((limits (save-excursion (when (looking-back "^") (backward-char 1)) (when (looking-back "[;}]") (backward-char 1)) (c-declaration-limits t)))) (cond (mark-active (concat (mst-region-string (region-beginning) (region-end)) "\n")) (limits (mst-region-string (car limits) (cdr limits))) (t (mst-region-string (line-beginning-position) (line-end-position))))))) (if print-result (bsh-send-command (format "System.err.println (String.valueOf (%s));" (trim-trailing command ";"))) (bsh-send-command command))))) ;; Python comint stuff (defun python-start () (interactive) (py-shell) (let ((proc (get-buffer-process (current-buffer)))) (comint-send-string proc "import sys\n") (comint-send-string proc "sys.ps2=\"\"\n"))) (defun python-send-command (s) (let ((proc (get-buffer-process "*Python*"))) (with-current-buffer (process-buffer proc) (set (make-local-variable 'comint-move-point-for-output) t) (goto-char (point-max)) (insert s) (comint-send-input)))) (defun python-send-defun-or-line () (interactive) (let ((command (cond (mark-active (concat (mst-region-string (region-beginning) (region-end)) "\n")) ((or (ignore-errors (py-mark-def-or-class t)) (ignore-errors (py-mark-def-or-class))) (concat (mst-region-string (mark) (point)) "\n")) (t (mst-region-string (line-beginning-position) (line-end-position)))))) (python-send-command (with-temp-buffer (insert command) (delete-matching-lines "^ *$" (point-min) (point-max)) (buffer-string))))) (defun view-with-major-mode (&optional mode) "Create a clone of the current buffer in major-mode MODE." (interactive (list (intern (completing-read "Mode: " (mapcar (lambda (e) (list (symbol-name e))) (apropos-internal "-mode$" 'commandp)) nil t)))) (let ((buf (generate-new-buffer-name (buffer-name (current-buffer))))) (switch-to-buffer (make-indirect-buffer (current-buffer) buf))) (funcall mode)) (defun mst-shell-command (command) (with-temp-buffer (let ((return (call-process "sh" nil (current-buffer) nil "-c" command))) (cons (if (> (buffer-size) 0) (subseq (buffer-string) 0 -1) "") return)))) (defun cperl-reindent-defun () (interactive) (save-excursion (beginning-of-defun) (forward-list) (backward-list) (cperl-indent-exp))) ;; Run unit tests using the compile command. (defun test-with-compile (test-command) (do-once 'compilation-finish-functions `(lambda (buffer status) (with-current-buffer ,(current-buffer) (cond ((string-match "finished" status) (show-test-status 'passed) (set-window-configuration ,(current-window-configuration))) (t (show-test-status 'failed)))))) (ignore-errors (kill-buffer (funcall compilation-buffer-name-function major-mode))) (compile test-command) 'handled) (defun test-with-make () (interactive) (test-with-compile "make test")) (defun test-file-prepend (prefix file) (concat (file-name-directory file) "/" prefix (file-name-nondirectory file))) ;; The following code doesn't quite work, but it might be useful one day. (defun python-wait-for-test-results (string) (let ((status (cond ((string-match "errors=[1-9]" string) 'failed) ((string-match "failures=[1-9]" string) 'failed) ((string-match "errors=0.*failures=0" string) 'passed) (t nil)))) (when status (show-test-status status) (setq comint-preoutput-filter-functions (delq 'python-wait-for-test-results comint-preoutput-filter-functions))) string)) (defun run-python-tests () (interactive) (with-current-buffer "*Python*" (add-hook 'comint-preoutput-filter-functions 'python-wait-for-test-results nil t) (python-send-command "runner.run (suite)") 'handled)) (defun emacs-wiki->moin () "Convert the current emacs-wiki buffer to moin format." (interactive) (let* ((buffer (current-buffer)) (buffer-name (format "%s " (buffer-name (current-buffer)))) (new-buffer (get-buffer-create buffer-name))) (with-current-buffer new-buffer (erase-buffer) (insert-buffer buffer) (goto-char (point-min)) (while (search-forward-regexp "^\\(\\*+\\)" nil t) (delete-region (match-beginning 1) (match-end 1)) (beginning-of-line) (let ((length (- (match-end 1) (match-beginning 1)))) (insert (make-string length ?=)) (end-of-line) (insert (concat " " (make-string length ?=))))) (replace-regexp "\\[\\[\\([^[]+\\)\\]\\]" "[\"\\1\"]" nil (point-min) (point-max)) (replace-regexp "^\\( +\\)-" "\\1*" nil (point-min) (point-max)) ) (pop-to-buffer new-buffer))) (defvar *break-time* 10 "The number of seconds to wait during a typing break.") (defun stop-break () (setq ignore-keystrokes nil)) (defun start-break () (interactive) (setq ignore-keystrokes t) (run-with-timer *break-time* nil 'stop-break) (let ((inhibit-quit t)) (while ignore-keystrokes (message "This is a typing break. Go do something else.") (when (input-pending-p) (read-event) (message "You're meant to be resting!")) (sit-for 0.5)) (setq quit-flag nil)) (message "Break's over! Back on your head!")) (defun cperl-beginning-of-function () (interactive) (if (save-excursion (beginning-of-line) (looking-at "^sub")) (beginning-of-line) (unless (save-excursion (beginning-of-line) (looking-at "^{")) (let ((beginning-of-defun-function nil)) (beginning-of-defun))) (search-backward-regexp "^sub" nil t))) (defun cperl-end-of-function () (interactive) (cperl-beginning-of-function) (forward-list)) (defun cperl-send-function () (interactive) (let ((beg (save-excursion (cperl-beginning-of-function) (point))) (end (save-excursion (cperl-end-of-function) (point)))) (cperl-send-region beg end t) (message "Sent %d chars" (- end beg)))) (defun cperl-send-region (beg end &optional quiet) (interactive "r") (let ((region (buffer-substring-no-properties beg end))) (with-temp-buffer (insert region) (flet ((message (&rest ignored) nil)) (replace-regexp "#.*$" "" nil (point-min) (point-max)) (replace-string (string 10) " " nil (point-min) (point-max))) (cperl-send-command (buffer-string) quiet)))) (defun cperl-send-line () (interactive) (cperl-send-region (line-beginning-position) (line-end-position))) (defun cperl-send-command (s &optional quiet) (cperl-show) (let ((proc (get-buffer-process "*Perl*"))) (with-current-buffer (process-buffer proc) (set (make-local-variable 'comint-move-point-for-output) t) (if quiet (comint-send-string proc s) (goto-char (point-max)) (insert s) (comint-send-input))))) (defun cperl-show () (interactive) (cond ((get-buffer "*Perl*") (let ((old (selected-window))) (let ((buffer (pop-to-buffer "*Perl*"))) (select-window old) buffer))) (t (error "Perl isn't running")))) (defun cperl-repl () (interactive) (when (and (get-buffer "*Perl*") (y-or-n-p "Kill existing *Perl*? ")) (kill-buffer "*Perl*")) (if (get-buffer "*Perl*") (switch-to-buffer-other-window "*Perl*") (save-window-excursion (comint-run "perl-repl.pl") (rename-buffer "*Perl*"))) (with-current-buffer (cperl-show) (set (make-local-variable 'comint-move-point-for-output) t) (cperl-send-command "$^W = 0"))) (defun format-for-mode (beg end) (interactive "r") (let ((mode (intern (completing-read "Mode: " (mapcar (lambda (e) (list (symbol-name e))) (apropos-internal "-mode$" 'commandp)) nil t)))) (let ((buf (generate-new-buffer-name (buffer-name (current-buffer))))) (with-current-buffer (make-indirect-buffer (current-buffer) buf) (funcall mode) (let ((inhibit-read-only t)) (narrow-to-region beg end) (unwind-protect (indent-region (point-min) (point-max) nil) (widen))) (kill-buffer nil))))) (when (try-require 'timeclock) (defun timeclock-grok-time (time-string) (destructuring-bind (year month day hour min sec) (mapcar 'string-to-number (split-string time-string "[: /]")) (encode-time sec min hour day month year))) (defun timeclock-current-project-line () (save-excursion (beginning-of-line) (search-forward-regexp " \\([^ ]*\\)$") (match-string 1))) (defun timeclock-current-project-time () (save-excursion (beginning-of-line) (search-forward-regexp "^[io] \\(.*\\) [^ ]*$") (match-string 1))) (defun timeclock-total-project-time (project) (timeclock-reread-log) (let ((total 0)) (with-current-buffer (find-file-noselect "~/.timelog") (goto-char (point-min)) (while (search-forward-regexp "^i " nil t) (when (string= project (timeclock-current-project-line)) (let ((start-time (time-to-seconds (timeclock-grok-time (timeclock-current-project-time))))) (next-line 1) (let ((end-time (time-to-seconds (timeclock-grok-time (timeclock-current-project-time))))) (incf total (- end-time start-time))))))) (let* ((hours (truncate (truncate total 60) 60)) (minutes (- (truncate total 60) (* hours 60))) (seconds (- total (* hours 60 60) (* minutes 60)))) (format "%.2d:%.2d:%.2d" hours minutes seconds)))) (defun timeclock-project-time (project) (interactive (list (completing-read "Project: " (mapcar #'list timeclock-project-list)))) (message "Total time spent on %s: %s" project (timeclock-total-project-time project)))) (defun scale-image-to-frame (image) (let ((width (truncate (frame-pixel-width) 2)) (data (getf (cdr image) :data)) (type (getf (cdr image) :type))) (with-temp-buffer (set-buffer-multibyte nil) (insert data) (let ((coding-system-for-read 'binary) (coding-system-for-write 'binary) (buffer-file-coding-system 'binary)) (shell-command-on-region (point-min) (point-max) (format "convert -geometry %dx /dev/stdin /dev/stdout" width) nil t) `(image :type ,type :data ,(buffer-string)))))) (defun fit-this-image () (interactive) (let ((image (get-text-property (point) 'display))) (unless image (error "No image at point!")) (save-excursion (let (start end) (while (get-text-property (point) 'display) (backward-char 1)) (forward-char 1) (setq start (point)) (while (get-text-property (point) 'display) (forward-char 1)) (setq end (point)) (let ((inhibit-read-only t)) (set-text-properties start end `(display ,(scale-image-to-frame image)))))))) (defun run-all-java-tests () (let ((output (get-buffer-create "*junit*")) (inhibit-read-only t)) (with-current-buffer output (erase-buffer)) (let ((result (reduce #'+ (mapcar (lambda (class-file) (call-process "junit" nil output nil (file-name-sans-extension class-file))) (directory-files "." nil "Test.*class"))))) (if (= result 0) t (pop-to-buffer output) (compilation-mode) nil)))) (defun read-major-mode (&optional prompt) (intern (completing-read (or prompt "Major mode?:") (mapcar (lambda (e) (list (symbol-name e))) (apropos-internal "-mode$" 'commandp)) nil t))) (define-minor-mode inline-code-mode nil nil " code" nil (if inline-code-mode (let ((mode (read-major-mode "Major mode for code blocks?: "))) (mmm-mode 1) (mmm-ify :submode mode :front "" :back "")) (mmm-mode -1))) (defvar remote-buffers '()) (defun get-remote-buffer (host) (if (and (assoc host remote-buffers) (buffer-live-p (cdr (assoc host remote-buffers)))) (cdr (assoc host remote-buffers)) (let ((buffer (find-file-noselect (format "/%s:.remote" host)))) (with-current-buffer buffer (ignore-errors (kill-buffer (format " *%s remote*" host))) (rename-buffer (format " *%s remote*" host))) (push (cons host buffer) remote-buffers) buffer))) (defvar remote-shell-last-host nil) (defvar remote-shell-history '()) (defun remote-shell-do-command (host command &optional separate-errors) (let ((buffer (get-remote-buffer host))) (with-current-buffer buffer (let* ((output-buffer (get-buffer-create "*remote-out*")) (error-buffer (if separate-errors (get-buffer-create "*remote-errors*") output-buffer))) (let ((result (save-window-excursion (tramp-handle-shell-command command output-buffer error-buffer)))) (unwind-protect (list result (with-current-buffer output-buffer (buffer-string)) (if separate-errors (with-current-buffer error-buffer (buffer-string)) nil)) (kill-buffer output-buffer) (when separate-errors (kill-buffer error-buffer)))))))) (defun replace-buffer-contents (buffer contents) (let ((inhibit-read-only t)) (with-current-buffer buffer (erase-buffer) (fundamental-mode) (insert contents)))) (defun remote-shell-command (host command &optional output-buffer error-buffer) (interactive (list (read-from-minibuffer "Host: " remote-shell-last-host) (read-from-minibuffer "Command: " nil nil nil 'remote-shell-history))) (setq remote-shell-last-host host) (destructuring-bind (return-code output errors) (if (and output-buffer error-buffer (not (eq output-buffer error-buffer))) (remote-shell-do-command host command t) (remote-shell-do-command host command)) (if (or current-prefix-arg output-buffer error-buffer) (if current-prefix-arg (insert output) (when output-buffer (replace-buffer-contents output-buffer output)) (when error-buffer (replace-buffer-contents error-buffer errors))) (when (interactive-p) (if (string= output "") (when (interactive-p) (message "No output")) (let ((buffer (get-buffer-create "*Remote command output*"))) (replace-buffer-contents buffer output) (display-buffer buffer))))) (list return-code output errors))) (defun test-with-remote-compile (host command) (let ((result (remote-shell-command host command))) (if (zerop (car result)) t (let ((buffer (get-buffer-create "*Test result*"))) (with-current-buffer buffer (let ((inhibit-read-only t)) (erase-buffer) (insert (cadr result)) (compilation-mode))) (display-buffer buffer) nil)))) (defun w3m-with-saved-window-configuration (url new-window) (let ((window-configuration (current-window-configuration))) (unless (member (current-buffer) (w3m-list-buffers)) (select-window (split-window-vertically))) (w3m-browse-url url nil) (let ((hs-map (copy-keymap w3m-mode-map))) (define-key hs-map (kbd "q") `(lambda () (interactive) (kill-buffer nil) (set-window-configuration ,window-configuration))) (use-local-map hs-map)))) (provide 'emacs-homebrew)