NEW: For a prettier blog interface, see the Wordpress version!

Tasks
What I use Emacs for
Evolution
Notes

Tasks

B1XWrite function to automatically mirror a page to my hard disk {{Tasks:993}} (2004.04.28)
B2XWrite pcmpl-arch {{Tasks:1030}} (2004.05.06)
B3XFix emacspeak-config so that it doesn't speak my deleted characters. (2004.11.20)
B0XFix nonexistent link problem: ../emacs/dev/emacs-wiki/emacs-wiki-publish.el (2004.12.18)
C1XFind out how to limit to unanswered mail {{Tasks:255}} (2004.01.11)
C2XMake screenshots {{Tasks:317}} (2004.03.21)

What I use Emacs for

Evolution

Like almost all newbies, I started out with pico, the editor that comes with pine. pico was friendly. pico was easy to use. pico also kept wrapping my lines, which is a Very Bad Idea when you're programming. I discovered that pico -w would turn such undesirable behavior off, and alias pico="pico -w " became part of my .bashrc everywhere.

However, I felt mildly ashamed of pico. All the Unix books said I should learn how to use vi, so I did. vi was fun, too - vim, especially. I had my funky one-line ex commands, like :s%/foo/bar/g. I could go to any line with :linenumber. I regularly used :! to invoke shell commands. I liked the way it syntax-colored practically all the files I edited - even the more obscure ones - and I was even thinking of writing my own syntax files for the things it didn't handle yet.

I suppose it was sheer curiosity that made me try out Emacs. Emacs was an intellectual challenge. I found myself attracted to its intimidating complexity. I wanted to see if I could get the hang of it.

Emacs was surprisingly easy to use. I read through the tutorial. I even browsed through the info node in my spare time. I used the menu bar and the tool bar until I learned the different shortcuts and extended commands. It was pretty cool.

Then one summer, I opened the Emacs LISP intro manual. I got hooked. I started reading Emacs source code. I traced through functions. I wrote my own. I did more and more stuff in Emacs and I realized how much I had missed by using other editors.

Emacs is cool. =)

Notes

49. Automatically scheduling tasks onto TaskPool

(defadvice planner-create-task-from-buffer (before paul activate)
  "Automatically schedule task onto TaskPool as well as other projects."
  (if plan-page
      (unless (string-match plan-page "TaskPool")
        (setq plan-page (concat plan-page planner-multi-separator "TaskPool")))
    (setq plan-page "TaskPool")))
Here's another snippet that will unschedule tasks from TaskPool when you mark them completed with C-c C-x (planner-task-done). Nifty stuff, huh?

(defadvice planner-task-done (after paul activate)
  "Remove completed tasks from the TaskPool if that still leaves them linked."
  (let ((info (planner-current-task-info)))
    (when (planner-task-link-text info)
      ;; If it is linked to TaskPool _and_ at least one other thing
      (if (string-match planner-multi-separator (planner-task-link-text info))
          (planner-multi-replan-task
           (planner-multi-make-link (planner-multi-filter-links "^TaskPool$" (planner-multi-task-link-as-list info) t)))
        ;; Else if it has a date and is linked to TaskPool
        (if (and (planner-task-date info)
                 (string= (planner-task-link info) "TaskPool"))
            (planner-replan-task nil))))))
原始的な計算機が、コンピューターの開発されるずっと以前に存在していた。 Primitive calculating machines existed long before computers were developed.

48. Adding an arbitrary extension to your planner files

Do you want all of your planner files to have a TXT extension so that they'll be searched by tools such as Google Desktop or Apple Spotlight?

Use the 'rename' tool to add .txt extensions to all of your files, or use the following code to rename all of your planner files from a running Planner setup:

(defun my-rename-planner-files ()
  "Rename all my planner files to .txt if they don't have that extension yet."
  (interactive)
  (mapcar
   (lambda (file)
     (unless (string-match "\\.txt$" (cdr file))
       (rename-file (cdr file) (concat (cdr file) ".txt"))
       (message "%s" (cdr file))))
   (planner-file-alist))
  (with-planner
    (emacs-wiki-refresh-file-alist)))
Then add this to your ~/.emacs before you load Planner:

(setq emacs-wiki-ignored-extensions-regexp "\\.txt")
    
(defadvice emacs-wiki-find-file (around extension activate)
  "Open the Emacs Wiki page WIKI by name.
If COMMAND is non-nil, it is the function used to visit the file.
If DIRECTORY is non-nil, it is the directory in which the Wiki
page will be created if it does not already exist."
  (unless (interactive-p)
    (setq wiki (cons wiki
                     (cdr (assoc wiki (emacs-wiki-file-alist))))))
  ;; At this point, `wiki' is (GIVEN-PAGE FOUND-FILE).
  (if (cdr wiki)
      (let ((buffer (funcall (or command 'find-file) (cdr wiki))))
        (if (= (prefix-numeric-value current-prefix-arg) 16)
            (with-current-buffer buffer
              (set (make-variable-buffer-local 'emacs-wiki-directories)
                   (cons (file-name-directory (cdr wiki))
                         emacs-wiki-directories))
              (set (make-variable-buffer-local 'emacs-wiki-file-alist)
                   nil)))
        buffer)
    (let* ((dirname (or directory
                        (emacs-wiki-maybe t)
                        (car emacs-wiki-directories)))
           (filename (expand-file-name (car wiki) dirname)))
      (unless (file-exists-p dirname)
        (make-directory dirname t))
      (funcall (or command 'find-file) (concat filename ".txt")))))
新しいパソコンを買うつもりで金を溜めているんだ。 I am saving money in order to buy a new personal computer.

47. BBDB tags

Right, that tags thing looks like a good idea. It should be easy to hack into BBDB. I'll need to actually tag people, and then write an Emacs Lisp script that scans through all of the records, gathers them into categories, and then creates the list.

HEY. This might actually work. Here's a quick test of tags:

ateneoCharles Yeung,Sean Uy,Ryan Kristoffer Tan,Stephanie Sy,Bit Santos,Jerome Punzalan
pisayJerome Punzalan,clair ching,mario carreon
linuxEric Pareja,Jared Odulio,Chris G. Haravata,levi guerrero,Zak B. Elep,clair ching,Dean Michael Berris,Jan Alonzo
blogCharles Yeung,Sean Uy,Ryan Kristoffer Tan,Stephanie Sy,Aaditya Sood,Bit Santos,Raven,Jerome Punzalan,Richard Plana,Phillip Pearson,Eric Pareja,Jared Odulio,Celsus Kintanar,Jan Michael Ibanez,Mark A. Hershberger,Chris G. Haravata,levi guerrero,Cha Gascon,Sim Gamboa, III,Marcelle Fabie,Zak B. Elep,David Edmondson,edelgado,Dominique Cimafranca,clair ching,Sean Champ,Dean Michael Berris,Jason Banico,John S. J. Anderson,Jan Alonzo
debianFederico Sevilla III,Paul Lussier,Angus Lees,Frederik Fouvry,Zak B. Elep,Joe Corneli,clair ching,Sean Champ,Miles Bader,Jan Alonzo,Jesse Alama
emacsManoj Srivastava,Paul Lussier,Lukhas,Angus Lees,Mario Lang,Jan Michael Ibanez,Mark A. Hershberger,Frederik Fouvry,clair ching,Miles Bader,Ethan Aubin,John S. J. Anderson,Jesse Alama
plannerPaul Lussier,Mark A. Hershberger,Frederik Fouvry,Zak B. Elep,Joe Corneli,clair ching,Ethan Aubin,John S. J. Anderson,Jesse Alama

Use C-o to add a "tags" field to your BBDB records. This should be a space-delimited list of tags (case-sensitive for now). Call M-x sacha/planner-bbdb-insert-tags-alist to produce a list like the one above.

(defun sacha/bbdb-get-tags (record)
  "Return the tags for RECORD as a list."
  (let ((tags (bbdb-record-getprop record 'tags)))
    (when tags (split-string tags)))) 

(defun sacha/bbdb-test-tags (query tags)
  "Return non-nil if QUERY is a subset of TAGS."
  (let ((result t))
    (while (and result query)
      (unless (member (car query) tags)
        (setq result nil))
      (setq query (cdr query)))
    result))
  
(defun sacha/bbdb-search-tags-internal (records tags)
  "Return a list of RECORDS matching TAGS."
  (when (stringp tags) (setq tags (split-string tags)))
  (let (result)
    (while records
      (when (sacha/bbdb-test-tags tags
                                  (sacha/bbdb-get-tags (car records)))
        (setq result (cons (car records) result)))
      (setq records (cdr records)))
    result))

(defun sacha/bbdb-search-tags (tags)
  "Display all the records that match TAGS."
  (interactive "MTags: ")
  (bbdb-display-records (sacha/bbdb-search-tags-internal (bbdb-records) tags)))

(defun sacha/planner-bbdb-link (record)
  "Return a link to RECORD."
  (or (bbdb-record-getprop record 'plan)
      ;; From a BBDB entry with a plan page; use that. Yay!
      (concat "[[bbdb://"
              (emacs-wiki-replace-regexp-in-string
               " " "."
               (bbdb-record-name record))
              "][" (bbdb-record-name record)
              "]]")))

(defun sacha/bbdb-get-tags-index ()
  "Return a list of tags and records."
  (let ((tags-alist '())
        (records (bbdb-records))
        tags
        entry
        list
        link)
    (while records
      (setq tags (sacha/bbdb-get-tags (car records)))
      (while tags
        (setq entry (assoc (car tags) tags-alist))
        (setq list (cdr entry))
        (add-to-list 'list (car records))
        (if entry
            (setcdr entry list)
          (add-to-list 'tags-alist (cons (car tags) list)))
        (setq tags (cdr tags)))
      (setq records (cdr records)))
    tags-alist))

(defun sacha/planner-bbdb-insert-tags-alist (&optional tag-alist)
  "Insert TAG-ALIST into the current buffer."
  (interactive)
  (unless tag-alist (setq tag-alist (sacha/bbdb-get-tags-index)))
  (insert (mapconcat
           (lambda (item)
             (concat (car item) " | "
                     (mapconcat
                      'sacha/planner-bbdb-link
                      (cdr item)
                      ",")))
           tag-alist
           "\n")))

To think that only took me an hour of leisurely coding (including tagging my contact information)...

昨年度のコンピューターからの利益は、今年度分よりも10%近く多かった。 Profit on computers for the previous year was nearly ten percent higher than the current year.

46. Modification of johnsu01's scoring

The following code allows you to sort tasks based on regexp matches against the line. It's fairly simple, but may give people ideas about fancier task sorting.

(setq planner-sort-tasks-key-function 'planner-sort-tasks-by-score)

(defvar planner-score-rules '(("read" . 50))
  "Alist of planner scoring rules of the form (regexp . score-value).
Tasks with higher scores are listed first.")

(defun planner-sort-tasks-by-score ()
  "Sort tasks by the rule in the table."
  (let ((score 0)                                                                                                                          
        (case-fold-search t)                                                                                                               
        (line (buffer-substring-no-properties (line-beginning-position)
                                              (line-end-position))))
    (mapc
     (lambda (item)
       (when (string-match (car item) line)
         (setq score (- score (cdr item)))))
     planner-score-rules)
    score))

45. flashcard-import-from-kill

The following code snippet makes it easier for me to import segments from my dictionary files. It uses ../emacs/flashcard.el.

(defun flashcard-import-from-kill (deck) 
  "Import cards for DECK from the clipboard, which should be colon-separated.

Question : Answer"
  (interactive (list flashcard-deck))
  (unless (eq major-mode 'flashcard-mode)
    (error "You're not in a deckfile."))
  (with-temp-buffer
    (yank)
    (goto-char (point-min))
    (while (re-search-forward "^ *\\(.*\\) +: +\\(.*\\)$" nil t)
      (flashcard-add-card deck
                          (flashcard-make-card (match-string 1)
                                               (match-string 2)))))
  (when (and (interactive-p)
             (not flashcard-card))
    (flashcard-ask)))
../emacs/flashcard-config.el

44. Proof of concept: Deleting private tasks

It's easy to tweak Planner. For example, the following two lines of code delete all lines that contain {{private}} before publishing. Your planner pages will be fine, but the published HTML won't contain them.

(add-to-list 'planner-publishing-markup (lambda () (delete-matching-lines "{{private}}")))
(planner-update-wiki-project)
Fun, isn't it?

43. Straight redefinition of describe-function-1

(defun describe-function-1 (function)
  (let* ((def (if (symbolp function)
		  (symbol-function function)
		function))
	 file-name string
         (beg (if (commandp def) "an interactive " "a ")))
    (setq string
	  (cond ((or (stringp def)
		     (vectorp def))
		 "a keyboard macro")
		((subrp def)
		 (if (eq 'unevalled (cdr (subr-arity def)))
		     (concat beg "special form")
		   (concat beg "built-in function")))
		((byte-code-function-p def)
		 (concat beg "compiled Lisp function"))
		((symbolp def)
		 (while (symbolp (symbol-function def))
		   (setq def (symbol-function def)))
		 (format "an alias for `%s'" def))
		((eq (car-safe def) 'lambda)
		 (concat beg "Lisp function"))
		((eq (car-safe def) 'macro)
		 "a Lisp macro")
		((eq (car-safe def) 'autoload)
		 (setq file-name (nth 1 def))
		 (format "%s autoloaded %s"
			 (if (commandp def) "an interactive" "an")
			 (if (eq (nth 4 def) 'keymap) "keymap"
			   (if (nth 4 def) "Lisp macro" "Lisp function"))
			 ))
                ((keymapp def)
                 (let ((is-full nil)
                       (elts (cdr-safe def)))
                   (while elts
                     (if (char-table-p (car-safe elts))
                         (setq is-full t
                               elts nil))
                     (setq elts (cdr-safe elts)))
                   (if is-full
                       "a full keymap"
                     "a sparse keymap")))
		(t "")))
    (princ string)
    (with-current-buffer standard-output
      (save-excursion
	(save-match-data
	  (if (re-search-backward "alias for `\\([^`']+\\)'" nil t)
	      (help-xref-button 1 'help-function def)))))
    (or file-name
	(setq file-name (symbol-file function)))
    (when (equal file-name "loaddefs.el")
      ;; Find the real def site of the preloaded function.
      ;; This is necessary only for defaliases.
      (let ((location
	     (condition-case nil
		 (find-function-search-for-symbol function nil "loaddefs.el")
	       (error nil))))
	(when location
	  (with-current-buffer (car location)
	    (goto-char (cdr location))
	    (when (re-search-backward
		   "^;;; Generated autoloads from \\(.*\\)" nil t)
	      (setq file-name (match-string 1)))))))
    (when (and (null file-name) (subrp def))
      ;; Find the C source file name.
      (setq file-name (if (get-buffer " *DOC*")
			  (help-C-file-name def 'subr)
			'C-source)))
    (when file-name
      (princ " in `")
      ;; We used to add .el to the file name,
      ;; but that's completely wrong when the user used load-file.
      (princ (if (eq file-name 'C-source) "C source code" file-name))
      (princ "'")
      ;; Make a hyperlink to the library.
      (with-current-buffer standard-output
        (save-excursion
	  (re-search-backward "`\\([^`']+\\)'" nil t)
	  (help-xref-button 1 'help-function-def function file-name))))
    (princ ".")
    (terpri)
    (when (commandp function)
      (let* ((remapped (command-remapping function))
	     (keys (where-is-internal
		    (or remapped function) overriding-local-map nil nil))
	     non-modified-keys)
	;; Which non-control non-meta keys run this command?
	(dolist (key keys)
	  (if (member (event-modifiers (aref key 0)) '(nil (shift)))
	      (push key non-modified-keys)))
	(when remapped
	  (princ "It is remapped to `")
	  (princ (symbol-name remapped))
	  (princ "'"))

	(when keys
	  (princ (if remapped " which is bound to " "It is bound to "))
	  ;; FIXME: This list can be very long (f.ex. for self-insert-command).
	  ;; If there are many, remove them from KEYS.
	  (if (< (length non-modified-keys) 10)
	      (princ (mapconcat 'key-description keys ", "))
	    (dolist (key non-modified-keys)
	      (setq keys (delq key keys)))
	    (if keys
		(progn
		  (princ (mapconcat 'key-description keys ", "))
		  (princ ", and many ordinary text characters"))
	      (princ "many ordinary text characters"))))
	(when (or remapped keys non-modified-keys)
	  (princ ".")
	  (terpri))))
    (let* ((arglist (help-function-arglist def))
	   (doc (documentation function))
	   (usage (help-split-fundoc doc function)))
      (with-current-buffer standard-output
        ;; If definition is a keymap, skip arglist note.
        (unless (keymapp def)
          (let* ((use (cond
                        (usage (setq doc (cdr usage)) (car usage))
                        ((listp arglist)
                         (format "%S" (help-make-usage function arglist)))
                        ((stringp arglist) arglist)
                        ;; Maybe the arglist is in the docstring of the alias.
                        ((let ((fun function))
                           (while (and (symbolp fun)
                                       (setq fun (symbol-function fun))
                                       (not (setq usage (help-split-fundoc
                                                         (documentation fun)
                                                         function)))))
                           usage)
                         (car usage))
                        ((or (stringp def)
                             (vectorp def))
                         (format "\nMacro: %s" (format-kbd-macro def)))
                        (t "[Missing arglist.  Please make a bug report.]")))
                 (high (help-highlight-arguments use doc)))
            (insert (car high) "\n")
            (setq doc (cdr high))))
        (let ((obsolete (and
                         ;; function might be a lambda construct.
                         (symbolp function)
                         (get function 'byte-obsolete-info))))
          (when obsolete
            (princ "\nThis function is obsolete")
            (when (nth 2 obsolete)
              (insert (format " since %s" (nth 2 obsolete))))
            (insert ";\n"
                    (if (stringp (car obsolete)) (car obsolete)
                      (format "use `%s' instead." (car obsolete)))
                    "\n"))
          (insert "\n"
                  (or doc "Not documented.")))
        (unless file-name
          (insert "\n\nDefinition hyperlink not found, so inserting definition here.\n\n"
                  (pp-to-string (symbol-function function))))))))

Chat on sterling.freenode.net#emacs

42. Code to print function definitions for code not associated with a file

This needs to be tweaked so that it notices when code has been redefined.

(defadvice describe-function-1 (after sacha activate)
  (let ((def (if (symbolp function)
                 (symbol-function function)
               function))
        file-name)
    ;; START OF CODE EXTRACTED FROM describe-function-1
    (and (eq (car-safe def) 'autoload) (setq file-name (nth 1 def)))
    (or file-name (setq file-name (symbol-file function)))
    (when (equal file-name "loaddefs.el")
      ;; Find the real def site of the preloaded function.
      ;; This is necessary only for defaliases.
      (let ((location
             (condition-case nil
                 (find-function-search-for-symbol function nil "loaddefs.el")
               (error nil))))
        (when location
          (with-current-buffer (car location)
            (goto-char (cdr location))
            (when (re-search-backward
                   "^;;; Generated autoloads from \\(.*\\)" nil t)
              (setq file-name (match-string 1)))))))
    (when (and (null file-name) (subrp def))
      ;; Find the C source file name.
      (setq file-name (if (get-buffer " *DOC*")
                          (help-C-file-name def 'subr)
                        'C-source)))
    ;; START NEW CODE
    (or file-name
        (with-current-buffer standard-output
          (insert "\n\nDefinition file not found, so inserting definition here.\n\n")
          (prin1 (symbol-function function))))))

Chat with :edrx on sterling.freenode.net#emacs

41. Japanese flashcards

This extracts all kanji in the buffer and converts them to the format expected by flashcard.el.

(defun sacha/kanji/get-ordered-kanji-list ()
  "Return a list of characters in the buffer."
  (goto-char (point-min))
  (let (kanji-list)                      
    (while (not (eobp))
      (let ((c (char-after (point))))
        (when (>= c ?亜) (add-to-list 'kanji-list c)))
      (forward-char 1))
    kanji-list))

(defun sacha/kanji/to-flashcard-j2e (&optional list)
  "Return a Japanese-English flashcard set.
If LIST is non-nil, use that instead of the current buffer."
  (interactive (list (sacha/kanji/get-ordered-kanji-list)))
  (unless list (setq list (sacha/kanji/get-ordered-kanji-list)))
  (let ((result
         (with-current-buffer (find-file-noselect "/usr/share/edict/kanjidic")
           (mapconcat
            (lambda (kanji)
              (goto-char (point-min))
              (when (re-search-forward (format "^%c.*?{\\(.*\\)}" kanji) nil t)
                (format "%c : %s\n"
                        kanji
                        (replace-regexp-in-string "}\\s-+{" "," (match-string 1)))))
            list
            ""))))
    (if (interactive-p) (kill-new result) result)))

(defun sacha/flashcard-method-leitner-check-answer (card answer)
  "Check answer for correctness. Allow multiple correct answers and provide feedback."
  (if (member answer (split-string (flashcard-card-answer card) ","))
      (progn 
        (flashcard-insert "Correct! Answer is:\n"
                          (propertize (flashcard-card-answer card)
                                      'face 'flashcard-answer-face
                                      'rear-nonsticky t)
                          "\n"
                          "\n")
        t)
    (flashcard-insert "The correct answer is:\n"
                      (propertize (flashcard-card-answer card)
                                  'face 'flashcard-answer-face
                                  'rear-nonsticky t)
                      "\n"
                      "\n")
    (y-or-n-p "Was your answer correct? ")))

(setq flashcard-method-check-answer-function 'sacha/flashcard-method-leitner-check-answer)
(add-to-list 'auto-mode-alist '("\\.deck\\'" . flashcard-mode))
(add-hook 'flashcard-mode-hook 'flashcard-add-scroll-to-bottom)
(add-hook 'flashcard-positive-feedback-functions 'flashcard-feedback-highlight-answer)
(add-hook 'flashcard-positive-feedback-functions 'flashcard-feedback-congratulate)
(add-hook 'flashcard-positive-feedback-functions 'flashcard-method-leitner-positive-feedback)

40. More hacks for mangling Japanese CSV

This is for use with kdrill.

(defun sacha/kanji/get-ordered-kanji-list ()
  (let (kanji-list)                      
    (while (not (eobp))
      (let ((c (char-after (point))))
        (cond
         ((= c ?\"))
         ((= c ?\n))
         ((= c ?:) (forward-line 1) (forward-char -1))
         (t (add-to-list 'kanji-list c))))
      (forward-char 1))
    kanji-list))

(defun sacha/kanji/ordered-usefile-to-kill ()
  (interactive)
  ;; Look up kanji in kanjidic
  (let ((list (sacha/kanji/get-ordered-kanji-list)))
    (kill-new
     (with-current-buffer (find-file-noselect "/usr/share/edict/kanjidic")
       (mapconcat
        (lambda (kanji)
          (goto-char (point-min))
          (when (search-forward (char-to-string kanji) nil t)
            (skip-syntax-forward " ")
            (buffer-substring-no-properties (point) (and (skip-syntax-forward "^ ") (point)))))
        list
        "\n")))))

39. Refactoring Planner annotation code

Many planner files just contain code for creating hyperlinks from the current buffer. This does not have anything to do with the idea of planning, but simply makes planning information available from more buffers.

If we separate this functionality from planner.el, we can make it easier for people to play around with context-sensitive hyperlinking without having to deal with planner's complexity.

To make it easy for other people to play around with this, the composition function needs to be flexible. Annotations should be returned as (uri text) pairs, and a -composition-function can put the two together in the appropriate format, escaping as necessary. Highlighting will be provided in a separate file that defines a minor mode that can be placed anywhere.

Other people's code for creating annotations, then, would just involve calling -get-uri, or -as-kill, or -to-string. We can use the hook mechanism to get the appropriate annotations for the current buffer. -core.el will provide a method for resolving links, and it should be something that can be used as browse-url-browser-function.

planner code also specifies how to mark up links. If we're moving the annotation code into a layer that doesn't know about publishing, what will happen to the code? In that case, we will define URL transformation functions in either emacs-wiki or planner. Yes, that would work...

I need to think of a good name for it. uri.el? Yeah, that sounds okay.

38. Updating the timelog

I often update my task descriptions. We haven't found a neat way to do this in-buffer, so I use planner-edit-task-description. However, if I update the task description or replan a task, my timelog data gets out of date. This code snippet updates all matching tasks in the timelog, and can serve as an example for code that updates things after a task is edited.

(defadvice planner-replan-task (around sacha/planner-timeclock activate)
  "Update the timelog as well. Warning! Do not have duplicate tasks!"
  (let ((info (planner-current-task-info)))
    ad-do-it
    (with-current-buffer (find-file-noselect timeclock-file)
      (goto-char (point-min))
      (while (re-search-forward
              (concat
               "^. [^ ]+ [^ ]+ "
               "\\("
               (regexp-quote (planner-task-plan info))
               "\\)"
               ": "
               (regexp-quote (planner-task-description info))
               "$")
              nil t)
        (replace-match (ad-get-arg 0) t t nil 1))
      (save-buffer)
      (kill-buffer (current-buffer)))))

(defadvice planner-edit-task-description (around sacha/planner-timeclock activate)
  "Update the timelog as well. Warning! Do not have duplicate tasks!"
  (let ((info (planner-current-task-info)))
    ad-do-it
    (with-current-buffer (find-file-noselect timeclock-file)
      (goto-char (point-min))
      (while (re-search-forward
              (concat
               "^. [^ ]+ [^ ]+ "
               (regexp-quote (planner-task-plan info))
               ": "
               "\\("
               (regexp-quote (planner-task-description info))
               "\\)"
               "$")
              nil t)
        (replace-match (ad-get-arg 0) t t nil 1))
      (setq planner-timeclock-current-task (ad-get-arg 0))
      (save-buffer)
      (kill-buffer (current-buffer)))))

35. emacs-wiki snippet for getting a list of images from a dired buffer

(defun sacha/emacs-wiki-marked-images-as-kill ()
  "Return a list of images ready to be inserted into a wiki page."
  (interactive)
  (kill-new (mapconcat
             'emacs-wiki-make-link
             (dired-get-marked-files)
             "\n")))
bash script for timestamping and thumbnailing pictures

#!/bin/bash
SIZE=320x240
while [ -n "$1" ]; do
  if [ -f $1 ]; then
    DATE=$(date +"%Y%m%d-%H%M%S" --reference="$1")
    EXT=$(echo "$1" | sed 's/.*\././')
    echo $1
    cp $1 "$DATE$EXT"
    cp "$DATE$EXT" "thumb-$DATE$EXT"
    touch --reference="$1" "$DATE$EXT"
    if [ "$EXT" == ".jpg" ]; then
       convert -size $SIZE -resize $SIZE "$DATE$EXT" "thumb-$DATE$EXT"
       touch --reference="$1" "thumb-$DATE$EXT"    
    fi
  fi
  shift
done

34. sacha/planner-create-note-from-task

;; Improvements:
;; - Link back to the task? 
;; - Make it possible to have your note on another page?
(defun sacha/planner-create-note-from-task ()
  "Create a note based on the current task."
  (interactive)
  (let* ((task-info (planner-current-task-info))
         note-num)
    (when task-info
      (setq note-num (planner-create-note (planner-page-name)))
      (save-excursion
        (save-window-excursion
          (when (planner-find-task task-info)
            (planner-edit-task-description
             (concat (planner-task-description task-info) " "
                     (planner-make-link
                      (concat (planner-page-name) "#"
                              (number-to-string note-num))
                      "..."))))))
      (insert (planner-task-description task-info) "\n\n"))))

;; I use F9 p to go to today's page, anyway.
(define-key planner-mode-map (kbd "C-c C-n") 'sacha/planner-create-note-from-task)
../emacs/planner-config.el

33. ee-send-to-erc-channel

<edrx> (defun ee-send-to-erc-channel (channel line)
<edrx>   (if (not (get-buffer channel))
<edrx>       (error "There's no buffer called \"%s\"" channel))
<edrx>   (if (not (eq 'erc-mode (with-current-buffer channel major-mode)))
<edrx>       (error "The buffer \"%s\" is not an ERC buffer"))
<edrx>   (switch-to-buffer channel)
<edrx>   (goto-char (point-max))
<edrx>   (insert line)
<edrx>   (erc-send-current-line))
<edrx> ;;
<edrx> (defun find-fsbotanswer (question)
<edrx>   (interactive "sQuestion: ")
<edrx>   (ee-send-to-erc-channel "fsbot" question))
<edrx> ;;
<edrx> ;; Example: (find-fsbotanswer "conkeror?")
Chat on niven.freenode.net#emacs

32. Emacs lisp snippet for browsing referrer logs

(defun sacha/ffap-quick ()
  (interactive)
  (save-window-excursion
    (save-excursion
      (ffap))))
This makes browsing the output of ref.pl (previously blogged; it just extracts the referrer) much easier. I local-set-key it to RET. Hitting RET twice opens the URL as a w3m tab in the background.

31. Marking up note headlines with a permalink

(defun sacha/planner-markup-note ()
  "Replace note with marked-up span."
  (let ((id (concat
             emacs-wiki-bare-digits-anchor-prefix
             (match-string 1)))
        (val (match-string 1)))
    (replace-match
     (save-match-data
       (format "#%s\n** %s " id
               (planner-make-link
                (concat (emacs-wiki-page-name)
                        "#" val)
                (concat val ".")))))))

(defalias 'planner-markup-note 'sacha/planner-markup-note)

30. Navigating the kill ring

Snippet from bojohan:

(defun yank-rpop (arg) (interactive "*p") (yank-pop (- arg)))
Map this to M-Y and you'll be all set.

29. Etask: Gantt charts for Emacs

http://www.reneweichselbaum.com/etask.html

Wow, pretty! I should check this out...

28. AAAAARRRRRRRRGGGGGHHHH! I hate open source! In the nicest way possible, but still...

ARGH! I hate open source. <sniff> In the nicest way possible. <sniff> So I decided to be nice <sniff> and work on command completion in Emacs for the tla version control system <sniff> and I Google to check if it had been done before <sniff> but only come across messages saying people were interested in doing it... <sniff> Pour a few hours into it <sniff> adding completion for _every_ _darn_ _command_ <sniff> and a day after posting it on gnu.emacs.sources and the arch mailing list <sniff> I get a note asking how it's different from an existing module listed on the wiki <sniff!> And the existing implementation is more flexible, too! <sniff> (Although not as pretty in terms of coding conventions.) Story of my life, really, <sniff> every time I get a good idea <sniff> and work on it <sniff> someone's gone and done it <sniff> and better than I would've.

Chat on Ede.NL.EU.:.Org#linuxhelp

27. pcmpl-arch.el

All the commands documented in tla help can be completed using ../emacs/vc-arch/pcmpl-arch.el, a programmable completion module for the arch version control system. I'm getting the hang of pcomplete now... =)

26. Heavily tweaked w3m

I've decided to do even more things the Emacs Way. Emacs-w3m is a lot more customizable than Mozilla. This is Emacs we're talking about after all, so it's no surprise. ;)

I've set up a heavily tweaked keymap that might fit the way I browse: an insane number of tabs and a lot of remembering. The default keymaps favor QWERTY, but I've tweaked it for my Dvorak keyboard. Here are a few thoughts.:

, and .         cycle through the tabs
HTNS (all caps) navigate through the page per line
tn              scroll through the page like DEL and SPC
r               remember
By default, pages open in new tabs in the background.

See linked file for more details.

../../notebook/emacs/w3m-config.el

25. sacha/try-expand-emacs-wiki-name

(defun sacha/try-expand-emacs-wiki-name (old)
  "Expand a wiki name."
  (unless old
    (he-init-string (he-dabbrev-beg) (point))
    (setq he-expand-list
          (if (derived-mode-p 'emacs-wiki-mode)
              (delq nil
                    (mapcar
                     (lambda (item)
                       (when (string-match
                              (concat "^" (regexp-quote he-search-string))
                              (car item))
                         (planner-make-link (car item))))
                     (emacs-wiki-file-alist))))))
  (while (and he-expand-list
              (or (not (car he-expand-list))
                  (he-string-member (car he-expand-list) he-tried-table t)))
    (setq he-expand-list (cdr he-expand-list)))
  (if (null he-expand-list)
      (progn
        (if old (he-reset-string))
        nil)
    (progn
      (he-substitute-string (car he-expand-list) t)
      (setq he-expand-list (cdr he-expand-list))
      t)))
../../notebook/emacs/hippie-config.el

24. sacha/fix-tla-log {{04.04.21,EmacsHacks}} 11:17

(defun sacha/fix-tla-log ()
  "Correct a wrong commit.
Run this inside the arch subdirectory for the patch in your
repository, not your project tree."
  ;; Copy the log file
   (let*
       ((directory (car (file-expand-wildcards "*.patches")))
        modes
        (log-file
         (car
          (file-expand-wildcards
           (concat (file-name-as-directory directory)
                   "new-files-archive/{arch}/*/*/*/[email protected]/patch-log/patch-*"))))
        (tar-file
         (car (file-expand-wildcards "*.tar.gz")))
        log-md5 tar-md5)
     (when directory
       (if (file-newer-than-file-p "log" log-file)
           (progn
             (delete-file log-file)
             (copy-file "log" log-file))
         (delete-file "log")
         (copy-file log-file "log"))
       ;; Recreate the tar.gz
       (delete-file tar-file)
       (call-process "tar" nil nil nil "zcvf" tar-file directory)
       ;; Calculate checksums
       (with-temp-buffer
         (call-process "md5sum" nil t nil "log" tar-file)
         (goto-char (point-min))
         (re-search-forward "^\\([^ ]+\\)\\s-+log" nil t)
         (setq log-md5 (match-string 1))
         (re-search-forward "^\\([^ ]+\\)" nil t)
         (setq tar-md5 (match-string 1)))
       (with-temp-buffer
         (setq modes (file-modes "checksum"))
         (insert-file-contents "checksum")
         (goto-char (point-min))
         (re-search-forward "^Signature-for")
         (delete-region (point-min) (match-beginning 0))
         (re-search-forward "^md5\\s-+log\\s-+\\([^ ]+\\)$")
         (replace-match log-md5 t t nil 1)
         (re-search-forward "^md5\\s-+.+?\\.tar\\.gz\\s-+\\([^ ]+\\)$")
         (replace-match tar-md5 t t nil 1)
         (when (re-search-forward "BEGIN PGP SIGNATURE" nil t)
           (delete-region (line-beginning-position) (point-max)))
         (let ((pgg-output-buffer (current-buffer)))
           (pgg-sign t))
         (delete-file "checksum")
         ;; Sign the checksum
         (write-file "checksum")
         (set-file-modes "checksum" modes)))))

23. emacs-wiki-link-url: return relative links

(defadvice emacs-wiki-link-url (around sacha activate)
  "Return relative links if possible."
  ad-do-it
  (when ad-return-value
    (unless (emacs-wiki-wiki-url-p ad-return-value)
      (setq ad-return-value
            (file-relative-name
             ad-return-value
             (if (string-match "public_html" ad-return-value)
                 "../../public_html/notebook/plans"
               "../../notebook/plans")))
      (when (and sacha/emacs-wiki-use-absolute-url-flag
                 emacs-wiki-publishing-p)
        (setq ad-return-value
              (w3m-expand-url
               ad-return-value
               "http://sacha.sachachua.com/notebook/wiki/"))))))

22. Scheduling tasks in the diary

(defun sacha/planner-diary-schedule-task (time)
  "Add a diary entry for the current task at TIME."
  (interactive "MTime: ")
  (save-window-excursion
    (save-excursion
      (save-restriction
        (let ((info (planner-current-task-info)))
          (sacha/planner-diary-add-entry
           (planner-task-date info)
           (concat time " | " (planner-task-description info) "")))))))

(defun sacha/planner-diary-add-entry (date text &optional annotation)
  "Prompt for a diary entry to add to `diary-file'."
  (interactive
   (list
    (if (or current-prefix-arg
            (not (string-match planner-date-regexp (planner-page-name))))
        (planner-read-date)
      (planner-page-name))
    (read-string
     "Diary entry: ")))
  (save-excursion
    (save-window-excursion
      (make-diary-entry
       (concat
        (let ((cal-date (planner-filename-to-calendar-date date)))
          (calendar-date-string cal-date t t))
        " " text
        (or annotation
            (let ((annotation (run-hook-with-args-until-success
                               'planner-annotation-functions)))
              (if annotation
                  (concat " " annotation)
                "")))))
      (planner-goto date)
      (planner-diary-insert-diary-maybe))))
../../notebook/emacs/planner-config.el

21. Ignoring orkut addresses in BBDB

(defun sacha/bbdb-canonicalize-net-hook (addr)
  "Do not notice [email protected] addresses."
  (cond ((null addr) addr)
        ((string-match "member@orkut\\.com" addr) nil)
        (t addr)))
(setq bbdb-canonicalize-net-hook 'sacha/bbdb-canonicalize-net-hook)
../../notebook/emacs/bbdb-config.el

20. bbdb: prefix for sacha/try-expand-factoid-from-bbdb

To control expansion further, I've made a bbdb: prefix required. This will allow me to still properly use dabbrev expansion.

;; Particularly fun with ERC. I am now a bot!
(defun sacha/try-expand-factoid-from-bbdb (old)
  "Try to expand from BBDB. If OLD is non-nil, cycle through other possibilites."
  (unless old
      ;; First time, so search through the BBDB records for the factoid.
    (progn
      (he-init-string (he-dabbrev-beg) (point))
      (setq he-expand-list nil)
      (when (string-match "bbdb:\\(.+\\)" he-search-string)
        (setq he-search-string (match-string 1 he-search-string))
        (mapc
         (lambda (item)
           (setq he-expand-list (append he-expand-list (list (bbdb-record-getprop item 'blog))))
           (setq he-expand-list (append he-expand-list (list (bbdb-record-getprop item 'web))))
           (setq he-expand-list (append he-expand-list (list (car (bbdb-record-net item)))))
           (setq he-expand-list (append he-expand-list (list (bbdb-record-getprop item 'notes)))))
         (let ((notes (cons '* he-search-string)))
           (bbdb-search (bbdb-records)
                        he-search-string he-search-string he-search-string
                        notes nil)))
        (setq he-expand-list (delq nil he-expand-list)))))
  (while (and he-expand-list
              (or (not (car he-expand-list))
                  (he-string-member (car he-expand-list) he-tried-table t)))
    (setq he-expand-list (cdr he-expand-list)))
  (if (null he-expand-list)
      (progn
        (if old (he-reset-string))
        nil)
    (progn
      (he-substitute-string (car he-expand-list) t)
      (setq he-expand-list (cdr he-expand-list))
      t)))
../../notebook/emacs/hippie-config.el

19. Local file links should be transformed to relative file links if possible

(defadvice emacs-wiki-markup-link (around sacha activate)
  "Resolve the matched wiki-link into its ultimate <a href> form.
Images used the <img> tag."
  ;; avoid marking up urls that appear to be inside existing HTML
  (when (and (not (eq (char-after (point)) ?\"))
             (not (eq (char-after (point)) ?\>)))
    (let* (string
           (wiki-link (match-string 0))
           (url (emacs-wiki-link-url wiki-link))
           (name (emacs-wiki-escape-html-string
                  (emacs-wiki-wiki-visible-name wiki-link))))
      (when url
        (unless (emacs-wiki-wiki-url-p url)
          (setq url
                (file-relative-name
                 url
                 (if (string-match "public_html" url)
                     "../../public_html/notebook/plans"
                   "../../notebook/plans")))))
      (setq string
            (if (null url)
                (if (and emacs-wiki-serving-p
                         (emacs-wiki-editable-p
                          (emacs-wiki-wiki-base wiki-link)))
                    (format
                     "<a class=\"nonexistent\" href=\"editwiki?%s\">%s</a>"
                     (emacs-wiki-wiki-base wiki-link) name)
                  (format "<a class=\"nonexistent\" href=\"%s\">%s</a>"
                           emacs-wiki-maintainer name))
              (if (save-match-data
                    (string-match emacs-wiki-image-regexp url))
                  (if (string-equal url name)
                      (format "<img src=\"%s\" />" url)
                    (format "<img src=\"%s\" alt=\"%s\" />" url name))
                (if (save-match-data
                      (string-match emacs-wiki-image-regexp name))
                    (format "<a href=\"%s\"><img src=\"%s\" /></a>" url name)
                  (format "<a href=\"%s\">%s</a>" url name)))))
      (add-text-properties 0 (1- (length string))
                           '(rear-nonsticky (read-only) read-only
                                            t) string)
      (setq ad-return-value string))))
../../notebook/emacs/emacs-wiki-config.el

18. sacha/acm-submit-problem

(defun sacha/submit-acm-problem ()
  (interactive)
  (let ((buffer (current-buffer)))
    (gnus-fetch-group "mail.judge-acm")
    (gnus-summary-post-news)
    (save-excursion
      (when (re-search-forward "--text follows this line--" nil t)
        (forward-line)
        (insert-buffer-substring buffer)))))
../../.emacs

17. sacha/list-web-stats

Foo bar baz qux ba

(defun sacha/list-web-stats (prefix)
  (interactive (list current-prefix-arg))
  (goto-char (point-min))
  (let ((results) referrer page)
    (while (re-search-forward "GET \\([^ ]+\\) [^\"]+\"[^\"]+\"\\([^\"]+\\)" nil t)
      (setq page (match-string 1))
      (setq referrer (match-string 2))
      (when (string-match "\\(google\\.[^/]+\\)/search.+?q=\\([^&]+\\)"
                          referrer)
        (setq referrer (concat (match-string 1 referrer) "://" (match-string 2 referrer))))
      (add-to-list
       'results
       (if prefix
           (concat page " " referrer)
         (concat referrer " " page))))
    (setq results (sort results 'string<))
    (with-current-buffer (get-buffer-create "*Web stats*")
      (erase-buffer)
      (while results
        (insert (car results) "\n")
        (setq results (cdr results)))
      (pop-to-buffer (current-buffer)))))

16. total-difference

(defun sacha/total-difference (list)
  "Computes the sum of the differences between successive items in LIST."
  (let ((distance 0))
    (while (cdr list)
      (setq distance (+ (abs (- (car list) (car (cdr list)))) distance))
      (setq list (cdr list)))
    distance))
I used this to calculate the total distance travelled by a read/write head given the list of tracks.

15. Display subject on the first line, dynamically

;;; gnus-summary-update-subject.el --- Display subject on the first line, dynamically

;;; Time-stamp: <2004-03-07 18:02:03 bojohan>
;; <http://www.dd.chalmers.se/~bojohan/emacs/gnus-summary-update-subject.html>

;; Modified by Sacha to work with subjects anywhere in the summary line.

;;; Code:

(defvar gnus-summary-update-subject-overlay nil)
(defvar gnus-summary-update-subject-hide-overlay nil)
(setq gnus-summary-same-subject (propertize " " 'gnus-summary-same-subject t))

(defvar gnus-summary-update-subject-thread-marker ">")

(defun gnus-summary-update-subject (&optional window start)
  (when gnus-summary-update-subject-overlay
    (save-excursion
      (goto-char (or start (window-start)))
      (when (re-search-forward gnus-summary-update-subject-thread-marker (line-end-position) t)
        (if (not (text-property-any (line-beginning-position)
                                    (line-end-position)
                                    'gnus-summary-same-subject t))
            (progn
              (delete-overlay gnus-summary-update-subject-overlay)
              (delete-overlay gnus-summary-update-subject-hide-overlay))
          (let ((subject (gnus-summary-subject-string
                          (gnus-summary-article-number))))
            (move-overlay gnus-summary-update-subject-hide-overlay
                          (+ (point) 2) (+ (point) 2 (length subject)))
            (move-overlay gnus-summary-update-subject-overlay
                          (point) (1+ (point)))
          ;;(overlay-put gnus-summary-subject-overlay 'window (selected-window))
            (overlay-put gnus-summary-update-subject-hide-overlay
                         'invisible t)
            (overlay-put gnus-summary-update-subject-hide-overlay
                         'intangible t)
            (overlay-put gnus-summary-update-subject-overlay
                         'after-string subject)))))))


(defun gnus-summary-update-subject-setup ()
  (add-hook 'window-scroll-functions 'gnus-summary-update-subject nil t)
  (set (make-local-variable 'gnus-summary-update-subject-overlay)
       (make-overlay 0 0))
  (set (make-local-variable 'gnus-summary-update-subject-hide-overlay)
       (make-overlay 0 0)))
(add-hook 'gnus-summary-prepared-hook 'gnus-summary-update-subject-setup)

(defadvice gnus-summary-position-point (after summary-update-subject activate)
  (gnus-summary-update-subject))

(provide 'gnus-summary-update-subject)

;;; gnus-summary-update-subject.el ends here
../../notebook/emacs/gnus-summary-update-subject.el

14. nowikilink

It would be nice to have a tag that turns off implicit wikiname linking...

13. ERC pseudo-AI assisted IRC help

 <sachac> Hmm. That gives me another nifty ERC idea - if we annotate BBDB records with timezones, we should be able to
          greet people good morning/day/evening appropriately. Plus points for greetings in native languages! ;)
 <sachac> Now that's just insane, really. <laugh>
 <arete> *augh*
 <arete> laugh too
 <arete> sacha: I'm sure you'll have it done by tomorrow ;)
 <myrkraverk> sachac: that could be nice, yes
 <myrkraverk> ,there is also an evil place without a name -- it does not have emacs
 <fsbot> Added entry to the term "there"
 <plaisthos> ,now
 <fsbot> try:  Acknowledgments NowPlaying PostItNow WikiAcknowledgments WikiNow
 <sachac> arete: Well, I've been thinking of rule-based matching on privmsgs received, with responses suggested in another
          buffer for easy selection with keysequence or mouse... =)
 <Lukhas> sachac: good idea
 <arete> hehe yeah, saw you mention it the other day
 <arete> just one step away from eliza =P
 <sachac> arete: I'm just thinking of how to do it nicely so that the matches don't take a terribly long time. I suppose
          match-string is my friend. I can build the regexp at the start, match it constantly, then match again based on
          the match string...
 <delYsid> ,df rx
 <fsbot> rx is a Lisp macro in `rx'.
 <fsbot> (rx REGEXP)
 <fsbot> Translate a regular expression REGEXP in sexp form to a regexp string.
 <fsbot> See also `rx-to-string' for how to do such a translation at run-time.
 <fsbot> The following are valid subforms of regular expressions in sexp
 <fsbot> notation.
 <fsbot> STRING ..[Type ,more]
 <sachac> arete: ... but of course that means I'll be working as a stateless machine for now. Oh well. Actually, no, the
          functions can keep state on their own; I just won't be able to add new keywords without recompiling the regular
          expression, which shouldn't be too hard.
 <myrkraverk> sachac: I guess the best way for the greeting is some standard text that gets translated at other ercs
 <arete> don't forget to weight the chosen responses so you don't have to look through them all each time the same choices
         come up =)
 <delYsid> sachac: use rx-to-string and a variable...
 <sachac> myrkraverk: Actually, that will hook into my "hi" thing...
 <myrkraverk> oh, k, then
 <sachac> myrkraverk: ERC should not only check which of your pals are online, but also which you haven't greeted yet, and
          people who aren't pals but who have greated you specifically. =)
 <sachac> I haven't written said "hi" thing yet, though.
 <sachac> Err.
 <sachac> Greeted.
 <sachac> Grrr.
 <sachac> myrkraverk: Ideally, ERC should compile a list of people to say Hi to, and hi them all on one line, appending a
          generic ", world" at the end or something like that. =)
 <sachac> myrkraverk: An extension to BBDB could have custom greetings for hi. For example, I greet some people in other
          languages.
 <myrkraverk> sachac: you have too many ppl to say hi to ;)
 <sachac> myrkraverk: It's a proof of concept! ;)
 <sachac> myrkraverk: If you tie that in with the funky timezone thing, that would be, well, pretty funky.
 <myrkraverk> btw, can erc let me know when someone is online?
 <arete> sacha: don't' forget the automated reply to a greeting =)
 <sachac> myrkraverk: However, the timezone thing could be done without the funky hi thing...
 <myrkraverk> hmm
 <sachac> arete: My proposed system wouldn't be entirely automated. It would suggest responses, but it would allow the
          user to actually change them or select a different response from the buffer.
 <zeDek> howdy sachac
 <sachac> arete: I'm thinking of a circular queue of 5 to 10 (of course, configurable) possible replies.
 <sachac> zeDek: howdy zeDek
 <sachac> Well, that's an easy way around it - just echo the greeting... ;)
 * sachac is the resident bot-in-training. ;)
 <zeDek> lol
 <myrkraverk> sachac: btw, I have a photo now on orkut (like you care :P )
 <arete> *chuckle*
 <Lukhas> zeDek: did you find time to send me the new color-theme ? :)
 <zeDek> Lukhas, ok wait
 <Lukhas> thanks
 <sachac> arete: I'm interested in this because I stay on a few help channels. My hippie-expansion from BBDB is pretty
          useful for expanding factoids, but (a) I don't want to have to remember what to expand, and (b) I want to be
          able to deal with questions I might not have paid attention to. ;)
 * zeDek was migrating old gnusfr.org and emacsfr.org
 <Lukhas> lukhas -> free point fr
 <sachac> myrkraverk: I'll check that out when I get around to starting up a graphical browser...
 <Lukhas> did you find hosting room ?
 <arete> sacha: now there is a more productive use of it =)
 <arete> I can never remember what keyword fsbot wants
 <sachac> delYsid: Hmmm, that should be interesting.
 <sachac> arete: The human still filters the automatic responses, of course. =)
 <delYsid> I use it in chess-ics for a very complicated re (760 chars)
 <sachac> arete: And automatic responses can be of the form <name>: <canned response> already...
 <sachac> arete: Naturally, if we allow functions and strings as canned responses, then we can even have a state machine.
 <arete> yumm, pseudo-AI assisted irc help
 <zeDek> Lukhas,
 <sachac> arete: Yeah, something like that. <laugh>
 <zeDek> Lukhas, done
 <sachac> arete: Worth hacking on in my spare time, I think.

12. ERC bot enhancements

I should add a hook that parses channel messages, extracts keywords, runs them through my database and populates a circular queue of likely "nick: canned answer" combinations.

11. Code for Nethack screenshots in Emacs

(defvar nethack-screenshot-file "~/.nethack-notes" "Filename to store Nethack data in.")
(defun sacha/nethack-take-screenshot (caption)
  (interactive "MCaption: ")
  (save-window-excursion
    (save-excursion
      (find-file nethack-screenshot-file)
      (goto-char (point-min))
      (insert
       ".#1 " caption "\n\n"
       (with-current-buffer (get-buffer "*nethack map*")
         (buffer-substring-no-properties (point-min) (point-max)))
       "\n\n"
       (with-current-buffer (get-buffer "*nethack status*")
         (buffer-substring-no-properties (point-min) (point-max)))
       "\n\n")
       (save-buffer))))

10. Good idea for nethack.el

From the Emacs channel:

kanaldrache: yes, therefor i want a mapping function ... take a picture of that level when you leave it so that you can chekc nine levels later "Mhh, where was that expensive shop? Level 4 or level 7?" without walking back or use you /oT

9. nethack code to check if a character has something in the inventory

(defvar nethack-temp-log nil "String containing captured messages.")

(defun nethack-has-item-p (regexp)
  "Return non-nil if the character has an item matching REGEXP in the inventory."
  (let ((nh-filter-hook (append (list 'nethack-store-log) nh-filter-hook))
        (nethack-temp-log nil))
    (nh-send-and-wait "inv 1")
    (nh-send nil)
    (string-match regexp nethack-temp-log)))

(defun nethack-store-log (string)
  (setq nethack-temp-log (concat nethack-temp-log string "\n")))

(defvar nh-filter-hook nil "Hook that is called with the received line.")
  
(defadvice nh-filter (around sacha activate)
  "Insert contents of STRING into the buffer associated with PROC.
Evaluate the buffer contents if we are looking at a prompt and then
delete the contents, perhaps logging the text."
  ;; insert output into process buffer
  (with-current-buffer (process-buffer proc)
    (goto-char (point-max))
    (insert string)
    (forward-line 0)
    (if (looking-at nh-prompt-regexp)
	(let ((prompt (match-string 1)))
          (nh-log (buffer-substring (point-min) (point)))
          (unless (run-hook-with-args-until-success 'nh-filter-hook (buffer-substring (point-min) (point))) 
	    (eval-region (point-min) (point)))
	  (cond ((or (equal prompt "command")
		     (equal prompt "menu"))
		 (nh-print-status)
		 (sit-for 0)
		 (setq nh-at-prompt t)))))))

8. Woohoo! Emacspeak back up!

After struggling with ../emacs/emacspeak-config.el, Emacs with Emacspeak now boots all the way up to my tasks. Now I get to play with it a whole lot more. Next step - get another Twiddler (Dr. Rodrigo said that the department will spring for it). Also, check out gestures for Emacs. Oh, and have lots of fun... =)

emacspeak-planner.el in ../emacs/emacs-wiki has preliminary support. emacspeak doesn't seem to like invisible text in emacs-wiki. Fix one of these days.

7. committing

The proper thing to do when C-x v v is called and files are modified depends on whether or not multiple files had been changed. If multiple files would be committed in a single patchset, one should bring up a dired buffer containing the modified files, allowing the user to unmark a few. When the files have been selected, a log window should appear. C-c C-c in this window should commit the patchset.

../../notebook/emacs/vc-arch/vc-arch.el

6. Emacs hacks

(defun sacha/reverse-line ()
  (interactive)
  (let ((string (buffer-substring (line-beginning-position) (line-end-position))))
    (delete-region (line-beginning-position) (line-end-position))
    (insert (apply 'string (reverse (string-to-list string))))))

5. Basic balance calculation

Moved into DoubleEntryAccounting.

4. Unanswered mail hack

I'm having a hard time keeping track of mail I've already answered. To better keep track of my mail, I think I will split off my personal mail into several groups:

  • mail.misc
  • mail.misc.archive for mail I've already answered and for my replies; threads I consider complete
  • mail.misc.noanswer for mail I want to archive but don't need to answer
  • mail.misc.all, a virtual group that lets me see and search through all the mail

All personal mail will be dumped into mail.misc.

EmacsHacks

2. Gnus pending

Categories: None -- Permalink, Comment form

Maybe I should just make tasks for them? Or perhaps use gnus-newsgroup-replied...

Xref: 2003.11.26:5

1. Smarter Gnus scoring

Categories: None -- Permalink, Comment form

I frequently want to see threads where I did not have the last word. How can I do that? Xref: 2003.11.26:4

36. Don't lose remember buffers when closing Emacs

(defun ajk/my-cleanup-then-save-buffers-kill-emacs (&optional arg)
  "Clean up before saving buffers and killing Emacs."
  (interactive "P")
  ;; stop here if there's a *Remember* buffer
  (if (get-buffer remember-buffer)
      (remember)
    ;; clean up Gnus
    (and
     (fboundp 'gnus-alive-p)
     (gnus-alive-p)
     (let ((gnus-interactive-exit nil))
       (gnus-group-exit)))
    (save-buffers-kill-emacs arg)))
(defalias 'sacha/save-buffers-kill-emacs
          'ajk/my-cleanup-then-save-buffers-kill-emacs)
../emacs/remember-config.el

37. Banning code for ERC

(defun erc-cmd-BAN (nick)
  (let* ((chan (erc-default-target))
         (who (erc-get-server-user nick))
         (host (erc-server-user-host who))
         (user (erc-server-user-login who)))
    (erc-send-command (format "MODE %s +b *!%s@%s" chan user host))))

(defun erc-cmd-KICKBAN (nick &rest reason)
  (setq reason (mapconcat #'identity reason " "))
  (erc-cmd-BAN nick)
  (erc-send-command (format "KICK %s %s :%s"
                            (erc-default-target)
                            nick
                            (or reason ""))))
Chat with :sindre on niven.freenode.net#emacs

Previous day | Next day

I'd love to hear about any questions, comments, suggestions or links that you might have. Your comments will not be posted on this website immediately, but will be e-mailed to me first. You can use this form to get in touch with me, or e-mail me at [email protected] .

Page: Emacs Hacks
Updated: 2005-06-09
NOTE: ANTI-SPAM MEASURE NOW IN PLACE. Please answer the following question with the right number in order to send me your comment.
What is two minus one? (hint: one ;) )
Name:
E-mail:
URL:
Comments: