;;; thinks.el --- Insert text in a think bubble. ;; Copyright 2000-2008 by Dave Pearson ;; $Revision: 1.9 $ ;; thinks.el is free software distributed under the terms of the GNU General ;; Public Licence, version 2 or (at your option) any later version. For ;; details see the file COPYING. ;;; Commentary: ;; thinks.el is a little bit of silliness inspired by the think bubbles you ;; see in cartoons. It allows you to ;; ;; . o O ( insert text that looks like this ) ;; ;; into a buffer. This could possibly be handy for use in email and usenet ;; postings. ;; ;; Note that the code can handle multiple lines ;; ;; . o O ( like this. That is, a body of text where the number of characters ) ;; ( exceeds the bounds of what you might consider to be a acceptable ) ;; ( line length (he says, waffling on to fill a couple of lines). ) ;; ;; You can also control how the bubble looks with `thinks-from'. The above ;; had it set to `top'. You can have `middle': ;; ;; ( like this. That is, a body of text where the number of characters ) ;; . o O ( exceeds the bounds of what you might consider to be a acceptable ) ;; ( line length (he says, waffling on to fill a couple of lines). ) ;; ;; `bottom': ;; ;; ( like this. That is, a body of text where the number of characters ) ;; ( exceeds the bounds of what you might consider to be a acceptable ) ;; . o O ( line length (he says, waffling on to fill a couple of lines). ) ;; ;; and `bottom-diagonal': ;; ;; ( like this. That is, a body of text where the number of characters ) ;; ( exceeds the bounds of what you might consider to be a acceptable ) ;; ( line length (he says, waffling on to fill a couple of lines). ) ;; O ;; o ;; . ;; ;; By default all of the thinking functions will fill (word wrap) the text ;; taking into account the value of `fill-column' minus the space required ;; for the bubble. Prefix a call to any of the functions with C-u to turn ;; off this behaviour. ;; ;; The latest thinks.el is always available from: ;; ;; ;;; Thanks: ;; Special thanks go to Gareth Owen for being barmy enough to be (as far as ;; we know) the first person to post to Usenet using thinks.el. If not the ;; first posting to Usenet at least the first posting to gnu.emacs.soruces. ;; ;; Thanks also go to Gareth for inspiring the extra-silliness option. ;; ;; Thanks to Jason Rumney for suggesting the diagonal option. ;; ;; Thanks to Martin Blais for `thinks-maybe-region'. ;;; Code: ;; Things we need: (eval-when-compile (require 'cl)) ;; Attempt to handle older/other emacs. (eval-and-compile ;; If customize isn't available just use defvar instead. (unless (fboundp 'defgroup) (defmacro defgroup (&rest rest) nil) (defmacro defcustom (symbol init docstring &rest rest) `(defvar ,symbol ,init ,docstring))) ;; If `line-beginning-position' isn't available provide one. (unless (fboundp 'line-beginning-position) (defun line-beginning-position (&optional n) "Return the `point' of the beginning of the current line." (save-excursion (beginning-of-line n) (point)))) ;; If `line-end-position' isn't available provide one. (unless (fboundp 'line-end-position) (defun line-end-position (&optional n) "Return the `point' of the end of the current line." (save-excursion (end-of-line n) (point))))) ;; Customize options. (defgroup thinks nil "Insert text in a think bubble." :group 'editing :prefix "thinks-") (defcustom thinks-bubbles ". o O " "*The lead-in think bubbles. Note that parts of the code assume that the string with always have even length and that every second character is a space. If you want to modify this string it is best that you stick to this format." :type 'string :group 'thinks) (defcustom thinks-main-bubble-left "( " "*The characters to use for the left hand side of the main bubble." :type 'string :group 'thinks) (defcustom thinks-main-bubble-right " )" "*The characters to use for the right hand side of the main bubble." :type 'string :group 'thinks) (defcustom thinks-from 'top "*Do we think from the TOP or the BOTTOM?" :type '(choice (const :tag "Think from the top of the bubble" top) (const :tag "Think from the middle of the bubble" middle) (const :tag "Think from the bottom of the bubble" bottom) (const :tag "Think diagonally from the bottom of the bubble" bottom-diagonal)) :group 'thinks) (defcustom thinks-extra-silliness nil "*Do we want some extra silliness? Note that the extra silliness only kicks in when `thinks-from' is set to `bottom' or `bottom-diagonal'." :type '(choice (const :tag "Yes, let's get really silly" t) (const :tag "No, I actually have a serious use for this" nil)) :group 'thinks) ;; Support code for working in different flavours of emacs. (defun thinks-xemacs-p () "Are we running in XEmacs?" (and (boundp 'running-xemacs) (symbol-value 'running-xemacs))) (defun thinks-mark-active-p () "Is there a mark active?" (if (thinks-xemacs-p) (funcall (symbol-function 'region-active-p)) (symbol-value 'mark-active))) ;; Main code: (defun thinks-bubble-wrap (text &optional no-filling) "Bubble wrap TEXT, don't fill if NO-FILLING is non-nil." (with-temp-buffer (let* ((extra-silly (and thinks-extra-silliness (or (eq thinks-from 'bottom) (eq thinks-from 'bottom-diagonal)))) (thinks-bubbles (concat (if extra-silly " " "") thinks-bubbles))) (insert text) (unless no-filling (let ((fill-column (- fill-column (+ (length thinks-bubbles) (length thinks-main-bubble-left) (length thinks-main-bubble-right))))) (fill-region (point-min) (point-max)))) (setf (point) (point-min)) (let ((max-line-width 0)) (save-excursion (while (not (eobp)) (setq max-line-width (max max-line-width (- (line-end-position) (point)))) (forward-line))) (let ((current-line 1) (total-lines (count-lines (point-min) (point-max))) (filler (make-string (length thinks-bubbles) 32))) (while (not (eobp)) (insert (cond ((and (eq thinks-from 'top) (= current-line 1)) thinks-bubbles) ((and (eq thinks-from 'bottom) (= current-line total-lines)) thinks-bubbles) ((and (eq thinks-from 'middle) (= current-line (truncate (/ (1+ total-lines) 2)))) thinks-bubbles) (t filler)) thinks-main-bubble-left) (save-excursion (end-of-line) (insert (make-string (- max-line-width (- (- (point) (line-beginning-position)) (+ (length thinks-bubbles) (length thinks-main-bubble-left)))) 32)) (insert thinks-main-bubble-right)) (incf current-line) (forward-line)))) (when (eq thinks-from 'bottom-diagonal) (unless (bolp) (insert "\n")) (loop for n downfrom (- (length thinks-bubbles) 2) to (if extra-silly 2 0) by 2 do (insert (make-string n 32) (substring thinks-bubbles n (1+ n)) "\n"))) (when extra-silly (setf (point) (point-max)) (unless (bolp) (insert "\n")) (insert " o\n/|\\\n |\n/ \\\n")) (buffer-string)))) ;;;###autoload (defun thinks (text) "Insert TEXT wrapped in a think bubble. Prefix a call to this function with \\[universal-argument] if you don't want the text to be filled for you." (interactive "sThinks: ") (unless (bolp) (insert "\n")) (insert (thinks-bubble-wrap text current-prefix-arg)) (insert "\n")) ;;;###autoload (defun thinks-region (start end) "Bubble wrap region bounding START and END. Prefix a call to this function with \\[universal-argument] if you don't want the text to be filled for you." (interactive "r") (let ((text (buffer-substring start end))) (save-excursion (delete-region start end) (setf (point) start) (insert (flet ((bolp-string (n) (save-excursion (setf (point) n) (if (bolp) "" "\n")))) (concat (bolp-string start) (thinks-bubble-wrap text current-prefix-arg) (bolp-string end))))))) ;;;###autoload (defun thinks-yank () "Do a `yank' and bubble wrap the yanked text. Prefix a call to this function with \\[universal-argument] if you don't want the text to be filled for you." (interactive) (thinks (with-temp-buffer (yank) (buffer-string)))) ;;;###autoload (defun thinks-maybe-region () "If region is active, bubble wrap region bounding START and END. If not, query for text to insert in bubble." (interactive) (if (thinks-mark-active-p) (call-interactively #'thinks-region) (call-interactively #'thinks))) (provide 'thinks) ;;; thinks.el ends here.