]> git.donarmstrong.com Git - lib.git/blob - emacs_el/dna-mode.el
ess configuration is now in don-configuration.org
[lib.git] / emacs_el / dna-mode.el
1 ;;; dna-mode.el --- a major mode for editing dna sequences
2 ;;
3 ;; ~harley/share/emacs/pkg/dna/dna-mode.el ---
4 ;;
5 ;; $Id: dna-mode.el,v 1.40 2004/04/20 19:03:04 harley Exp $
6 ;;
7
8 ;; Author:    Harley Gorrell <harley@panix.com>
9 ;; URL:       http://www.mahalito.net/~harley/elisp/dna-mode.el
10 ;; License:   GPL v2
11 ;; Keywords:  dna, emacs, editing
12
13 ;;; Commentary:
14 ;; * A collection of functions for editing DNA sequences.  It
15 ;;   provides functions to make editing dna in Emacs easier.
16 ;;
17 ;; Dna-mode will:
18 ;;  * Fontify keywords and line numbers in sequences.
19 ;;  * Fontify bases when font-lock-mode is disabled.
20 ;;  * Incrementally search dna over pads and numbers.
21 ;;  * Complement and reverse complement a region.
22 ;;  * Move over bases and entire sequences.
23 ;;  * Detect sequence files by content.
24
25 ;;; Installation:
26 ;; --------------------
27 ;; Here are two suggested ways for installing this package.
28 ;; You can choose to autoload it when needed, or load it
29 ;; each time emacs is started.  Put one of the following
30 ;; sections in your .emacs:
31 ;;
32 ;; ---Autoload:
33 ;;  (autoload 'dna-mode "dna-mode" "Major mode for dna" t)
34 ;;  (add-to-list
35 ;;     'auto-mode-alist
36 ;;     '("\\.\\(fasta\\|fa\\|exp\\|ace\\|gb\\)\\'" . dna-mode))
37 ;;  (add-hook 'dna-mode-hook 'turn-on-font-lock)
38 ;;
39 ;; ---Load:
40 ;;  (setq dna-do-setup-on-load t)
41 ;;  (load "/pathname/dna-mode")
42 ;;
43 ;; The dna-isearch-forward function (and isearch in general)
44 ;; is much more useful with something like the following:
45 ;;  (make-face 'isearch)
46 ;;  (set-face-background 'isearch "yellow")
47 ;;  (setq-default search-highlight t)
48
49 ;;; History:
50 ;;  2003-03-16: Updated URL and contact info
51 ;;  2004-04-20: Added dna-color-bases-region to the keymap for Mike.
52
53 ;;; User customizable vars start here
54
55 ;;; Code:
56 (defvar dna-mode-hook nil
57   "*Hook to setup `dna-mode'.")
58
59 (defvar dna-mode-load-hook nil
60   "*Hook to run when `dna-mode' is loaded.")
61
62 (defvar dna-setup-on-load nil
63   "*If not nil setup dna mode on load by running `dna-`add-hook's'.")
64
65 ;; Bases
66 (defvar dna-valid-base-regexp
67   "[-*:acgtmrwsykvhdbxnACGTMRWSYKVHDBXN]"
68   "*A regexp which matches a single base.")
69
70 (defvar dna-base-complement-list
71   '((?- . ?-) (?n . ?n) (?* . ?*) (?x . ?x) (?: . ?:) ; identity
72     (?a . ?t) (?c . ?g) (?g . ?c) (?t . ?a) ; single
73     (?m . ?k) (?r . ?y) (?w . ?w) (?s . ?s) (?y . ?r) (?k . ?m) ; double
74     (?v . ?b) (?h . ?d) (?d . ?h) (?b . ?v) ; triple
75     )
76   "*List of bases and their complements.
77 Bases should be lowercase, as they are upcased when the `vector is made.")
78
79 ;; These are the colors used when coloring bases.
80 (defvar dna-base-color-a "blue")
81 (defvar dna-base-color-c "black")
82 (defvar dna-base-color-g "green")
83 (defvar dna-base-color-t "red")
84
85 ;; Dna-isearch
86 (defvar dna-cruft-regexp "[* 0-9\t\n]"
87   "*Regexp to match cruft which may appear between bases.
88 Skip over it during dna-motion and dna-isearch.")
89
90 (defvar dna-isearch-case-fold-search t
91   "*Case fold dna-isearches if set.")
92
93 ;; Sequence
94 (defvar dna-sequence-start-regexp
95   "^\\(>\\|ID\\|LOCUS\\|DNA\\)"
96   "A regexp which matches the start of a sequence.")
97
98 ;;; End of user customizable vars
99
100 ;;; Start of internal vars and code
101
102 (defvar dna-base-complement-vector
103   (let ((c-vec (make-vector 256 nil))
104         (c-list dna-base-complement-list))
105     (while c-list
106       (aset c-vec (car (car c-list)) (cdr (car c-list)))
107       (aset c-vec (upcase (car (car c-list))) (upcase (cdr (car c-list))))
108       (setq c-list (cdr c-list)))
109     c-vec)
110   "A vector of upper and lower case bases and their complements.")
111
112 ;; I also use "Alt" as C-c is too much to type for cursor motions.
113 (defvar dna-mode-map
114   (let ((map (make-sparse-keymap)))
115     ;; Ctrl bindings
116     (define-key map "\C-c\C-f"  'dna-forward-base)
117     (define-key map "\C-cf"     'dna-forward-base)
118     (define-key map "\C-c\C-b"  'dna-backward-base)
119     (define-key map "\C-cb"     'dna-backward-base)
120     (define-key map "\C-c\C-s"  'dna-isearch-forward)
121     (define-key map "\C-cs"     'dna-isearch-forward)
122     (define-key map "\C-cr"     'dna-reverse-complement-region)
123     (define-key map "\C-cc"     'dna-complement-region)
124     (define-key map "\C-c#"     'dna-count-bases-region)
125     (define-key map "\M-\C-h"   'dna-mark-sequence)
126     (define-key map "\M-\C-a"   'dna-beginning-of-sequence)
127     (define-key map "\M-\C-e"   'dna-end-of-sequence)
128     ;; base coloring
129     (define-key map "\C-cg"     'dna-color-bases-region)
130     (define-key map "\C-cl"     'font-lock-mode)
131     ;; XEmacs does not like the Alt bindings
132     (when (not (string-match "XEmacs" (emacs-version)))
133       (define-key map [A-right] 'dna-forward-base)
134       (define-key map [A-left]  'dna-backward-base)
135       (define-key map [A-up]    'dna-beginning-of-sequence)
136       (define-key map [A-down]  'dna-end-of-sequence)
137       (define-key map [?\A-\C-s]        'dna-isearch-forward))
138     map)
139   "The local keymap for `dna-mode'.")
140
141 ;;;###autoload
142 (defun dna-mode ()
143   "Major mode for editing DNA sequences.
144
145 This mode also customizes isearch to search over line breaks.
146 Use \\[universal-argument] Number as a prefix to dna-forward-base to move that
147 many bases.  This skips line breaks and spaces.
148
149 dna-color-bases-region disables font-lock-mode automaticly
150 as they cant work together. \\[dna-color-bases-region] turns font-lock-mode back on.
151
152 \\{dna-mode-map}"
153   (interactive)
154   ;;
155   (kill-all-local-variables)
156   (setq mode-name "dna")
157   (setq major-mode 'dna-mode)
158   (use-local-map dna-mode-map)
159   ;;
160   (make-local-variable 'font-lock-defaults)
161   (setq font-lock-defaults '(dna-font-lock-keywords))
162   ;;
163   (make-local-variable 'dna-valid-base-regexp)
164   (make-local-variable 'dna-sequence-start-regexp)
165   (make-local-variable 'dna-cruft-regexp)
166   (make-local-variable 'dna-isearch-case-fold-search)
167   ;;
168   (run-hooks 'dna-mode-hook))
169
170 ;; Keywords
171 ;; Todo: Seperate the keywords into a list for each format, rather
172 ;; than one for all.
173 (defvar dna-font-lock-keywords
174   '(
175     ;; Fasta
176     ("^\\(>\\)\\([-_.|a-zA-Z0-9]+\\)\\([ \t]+.*\\)?"
177      (1 font-lock-keyword-face)
178      (2 font-lock-function-name-face)
179      (3 font-lock-comment-face nil t))
180
181     ;; Exp
182     ("^\\(ID\\) +\\([-_.a-zA-Z_0-9]+\\)"
183      (1 font-lock-keyword-face) (2 font-lock-function-name-face))
184     ("^\\(CC\\|SQ\\)\\([ \t]\\(.*\\)\\)?$"
185      (1 font-lock-keyword-face) (3 font-lock-comment-face nil t))
186     ("^\\(\\sw\\sw\\)[ \t]"
187      (1 font-lock-keyword-face))
188     ("^\\(//\\)"
189      (1 font-lock-keyword-face))
190
191     ;; Ace (phrap output)
192     ("^\\(DNA\\|Sequence\\|BaseQuality\\) +\\([-_.a-zA-Z_0-9]+\\)"
193      (1 font-lock-keyword-face) (2 font-lock-function-name-face))
194
195     ;; Genbank
196     ("^\\(LOCUS\\) +\\([-_.a-zA-Z_0-9]+\\)";; are '-_.' allowed?
197      (1 font-lock-keyword-face) (2 font-lock-function-name-face))
198     "ORIGIN"
199
200                                         ; More genbank keywords...
201     "ACCESSION" "AUTHORS" "AUTHORS" "BASE COUNT" "DEFINITION"
202     "FEATURES" "JOURNAL" "JOURNAL" "KEYWORDS" "MEDLINE" "NID"
203     "ORGANISM" "REFERENCE" "SEGMENT" "SOURCE" "TITLE"
204
205     ;; line numbers...
206     ("^[ \t]*\\([0-9]+\\)"
207      (1 font-lock-string-face))
208
209     ;; others...?
210     )
211   "Expressions to hilight in `dna-mode'.")
212
213
214 ;;; Setup functions
215 (defun dna-find-file-func ()
216   "Invoke `dna-mode' if the buffer look like a sequence.
217 and another mode is not active.
218 This function is added to `find-file-hooks'."
219   (if (and (eq major-mode 'fundamental-mode)
220            (looking-at dna-sequence-start-regexp))
221     (dna-mode)))
222
223 ;;;###autoload
224 (defun dna-add-hooks ()
225   "Add a default set of dna-hooks.
226 These hooks will activate `dna-mode' when visiting a file
227 which has a dna-like name (.fasta or .gb) or whose contents
228 looks like dna.  It will also turn enable fontification for `dna-mode'."
229   (add-hook 'dna-mode-hook 'turn-on-font-lock)
230   (add-hook 'find-file-hooks 'dna-find-file-func)
231   (add-to-list
232    'auto-mode-alist
233    '("\\.\\(fasta\\|fa\\|exp\\|ace\\|gb\\)\\'" . dna-mode)))
234
235 ;; Setup hooks on request when this mode is loaded.
236 (if dna-setup-on-load
237   (dna-add-hooks))
238
239 (defun dna-next-char-func ()
240   "Should never be called.  Overridden in `dna-forward-base'."
241   (error "This shouldnt have been called"))
242
243 ;; Motion
244 (defun dna-forward-base (count)
245   "Move forward COUNT bases.  Move backward if negative.
246 Skip over dna-isearch-cruft.  Stop on non-base or
247 non-whitespace characters."
248   (interactive "p")
249   (let ((c 0)
250         (abscount (abs count))
251         (dir (if (< count 0) -1 1))
252         dna-next-char-func
253         bstr)
254     ;; 
255     (fset 'dna-next-char-func (if (< dir 0) 'preceding-char 'following-char))
256     ;;
257     (while (< c abscount)
258       (setq bstr (char-to-string (dna-next-char-func)))
259       (cond
260        ((string-match dna-valid-base-regexp bstr)
261         (forward-char dir)
262         (setq c (1+ c)))
263        ((string-match dna-cruft-regexp bstr)
264         (forward-char dir))
265        (t
266         (message "Moved %d bases forward." c)
267         (setq abscount c))))            ; stop the while
268
269     ;; Move over trailing junk when moving forward
270     (if (= dir 1)
271       (while (string-match dna-cruft-regexp
272                            (char-to-string (dna-next-char-func)))
273         (forward-char dir))
274       )
275     ;; return the distance moved
276     (* dir abscount)))
277
278 ;; aaaaaaaaaa cccccccccc | gggggggggg tttttttttt
279
280 (defun dna-backward-base (count)
281   "Move backward COUNT bases.  See `dna-forward-base'."
282   (interactive "p")
283   (dna-forward-base (- count)))
284
285 (defun dna-beginning-of-sequence ()
286   "Move the start of the sequence or the buffer."
287   (interactive)
288   (goto-char
289    (or
290     (search-backward-regexp dna-sequence-start-regexp (point-min) t)
291     (point-min))))
292
293 (defun dna-end-of-sequence ()
294   "Move to the end of the sequence or the buffer."
295   (interactive)
296   (end-of-line)
297   (skip-syntax-forward "-")
298   (let ((seqstart
299          (search-forward-regexp dna-sequence-start-regexp (point-max) t)))
300     (if seqstart (progn
301                    (goto-char seqstart)
302                    (beginning-of-line))
303         (goto-char (point-max)))))
304
305 (defun dna-mark-sequence ()
306   "Put point at the beginning of a sequence, mark at end."
307   (interactive)
308   (dna-end-of-sequence)
309   (set-mark (point))
310   (dna-beginning-of-sequence))
311
312 (defun dna-count-bases-region (d-start d-end)
313   "Count the number of bases in the region D-START to D-END.
314 Echos the number of bases counted.
315 If an invalid base is found, stops on the base and signals an error."
316   (interactive "r")
317   (let ((basecount 0))
318     (goto-char d-start)
319     (while (< (point) d-end)
320       (cond
321        ((looking-at dna-valid-base-regexp)
322         (setq basecount (1+ basecount))
323         (forward-char 1))
324        ((looking-at dna-cruft-regexp)
325         (forward-char 1))
326        (t
327         (error "Bad base found.  '%s'"
328                (buffer-substring (point) (1+ (point)))))
329        ))
330     (message "There are %d bases in the region." basecount)
331     basecount))
332
333 ;;; reverse and complement
334 (defun dna-complement-base-list (base)
335   "Complement the BASE using a list based method.
336 Returns the complement of the base.
337 It can also be used to test if the character is a base,
338 as all bases should have a complement."
339   (cdr (assq base dna-base-complement-list)))
340
341 (defun dna-complement-base (base)
342   "Complement a BASE using a vector based method.
343 See `dna-complement-base-list' for more info."
344   (aref dna-base-complement-vector base))
345
346 (defun dna-complement (base)
347   "Look up the complement of the BASE and print a message.
348 Handy for us CS types."
349   (interactive "cComplement of base:")
350   (message "Complement of '%c' is '%c'." base (dna-complement-base base)))
351
352 (defun dna-complement-region (r-start r-end)
353   "Complement a region of bases from R-START to R-END.
354 Complement a region of the buffer by deleting it and
355 inserting the complements, base by base.  Non-bases are
356 passed over unchanged."
357   (interactive "r")
358   (let (r-string r-length r-point r-base r-cbase)
359     (goto-char r-start)
360     (setq r-string (buffer-substring-no-properties r-start r-end))
361     (setq r-length (length r-string))
362     (delete-region r-start r-end)
363     (setq r-point 0)
364     (while (< r-point r-length)
365       (setq r-base (aref r-string r-point))
366       (setq r-cbase (dna-complement-base r-base))
367       (insert (if r-cbase r-cbase r-base))
368       (setq r-point (1+ r-point)))))
369
370 ;;;###autoload
371 (defun dna-reverse-complement-region (r-start r-end)
372   "Reverse complement a region of dna from R-START to R-END.
373 Works by deleting the region and inserting bases reversed
374 and complemented, while entering non-bases in the order
375 found."
376   (interactive "r")
377   (let (r-string r-length r-base r-cbase r-point r-mark)
378     (goto-char r-start)
379     (setq r-string (buffer-substring-no-properties r-start r-end))
380     (setq r-length (length r-string))
381     (setq r-mark (1- r-length))
382     (setq r-point 0)
383
384     ;; goodbye
385     (delete-region r-start r-end)
386
387     ;; insert the bases from back to front base by base
388     ;; insert non-bases from front to back to preserve spacing
389     (while (< r-point r-length)
390       (setq r-base (aref r-string r-point))
391       (setq r-cbase (dna-complement-base r-base))
392       (if r-cbase
393         (progn
394           ;; it is a base. find the reverse and complement it
395           (while (not (dna-complement-base (aref r-string r-mark)))
396             (setq r-mark (1- r-mark)))
397           (insert (dna-complement-base (aref r-string r-mark)))
398           (setq r-mark (1- r-mark)) )
399         ;; not a base, no change
400         (insert r-base))
401       (setq r-point (1+ r-point)))))
402
403 ;; format
404 (defun dna-guess-format-func ()
405   "Guess the format of the sequence the point is at or after.
406 Returns the format or nil."
407   (save-excursion
408     (end-of-line)
409     (dna-beginning-of-sequence)
410     (cond
411      ((looking-at "^>")   'fasta)
412      ((looking-at "^DNA") 'phrap)
413      ((looking-at "^ID")  'exp)
414      (t nil))))
415
416 (defun dna-guess-format ()
417   "Guess and print the format of the sequence."
418   (interactive)
419   (message "%s" (dna-guess-format-func)))
420
421 ;;; dna-isearch stuff
422 (defun dna-isearch-mangle-str (str)
423   "Mangle the string STR into a regexp to search over cruft in sequence.
424 Inserts a regexp between each base which matches sequence formatting cruft.
425 For example, if `dna-cruft-regexp' is            '[ ]',
426 the search string 'acgt' would transformed into  'a[ ]*c[ ]*g[ ]*t[ ]*'"
427   (let ((i 0) (out ""))
428     (while (< i (length str))
429       (setq out (concat out (substring str i (1+ i)) dna-cruft-regexp "*"))
430       (setq i (1+ i)))
431     out))
432
433 (defadvice isearch-message-prefix (around dna-isearch-ismp)
434   "Set the isearch prompt string to show dna search is active.
435 This serves as a warning that the string is being mangled."
436   ad-do-it
437   (setq ad-return-value (concat "DNA " ad-return-value)))
438
439 (defadvice isearch-search (around dna-isearch-iss)
440   "The advice used to mangle the search string in isearch."
441   (let ((isearch-regexp t)
442         ;; force case folding
443         (isearch-case-fold-search dna-isearch-case-fold-search)
444         (isearch-string (dna-isearch-mangle-str isearch-string)) )
445     ad-do-it))
446
447 ;;;###autoload
448 (defun dna-isearch-forward ()
449   "Isearch forward on dna sequence.
450 Enable the `dna-mode' search string mangling advice and start the search."
451   (interactive)
452   ;; Enable the prompt
453   (ad-enable-advice 'isearch-message-prefix 'around 'dna-isearch-ismp)
454   (ad-activate 'isearch-message-prefix)
455   ;; Enable the mangling
456   (ad-enable-advice 'isearch-search 'around 'dna-isearch-iss)
457   (ad-activate 'isearch-search)
458
459   ;; run the search
460   (isearch-forward)
461
462   ;;
463   (ad-disable-advice 'isearch-message-prefix 'around 'dna-isearch-ismp)
464   (ad-activate 'isearch-message-prefix)
465   ;; 
466   (ad-disable-advice 'isearch-search 'around 'dna-isearch-iss)
467   (ad-activate 'isearch-search))
468
469 ;;; Work with columns of sequences.
470
471 (defun dna-column-select-func ()
472   "Return the start and end of the column as a cons.
473 Point is moved forward one."
474   (let (s m e)
475     (setq m (point))
476     ;; work our way up
477     (while (looking-at dna-valid-base-regexp)
478       (setq s (point))
479       (previous-line 1))
480     (goto-char m)
481     ;; work our way down
482     (while (looking-at dna-valid-base-regexp)
483       (setq e (point))
484       (next-line 1))
485     (goto-char m)
486     ;; return the start and end of the column
487     (cons s (1+ e))))
488
489 (defun dna-column-select ()
490   "Select the current column of text.
491 Sets the mark at the top and the point at the bottom of a non-blank column."
492   (interactive)
493   (let ((se (dna-column-select-func)))
494     (goto-char (car se))
495     (push-mark)
496     (goto-char (cdr se))))
497
498 (defvar dna-column-pad "*"
499   "Character to use when inserting a column of pads.")
500
501 (defun dna-column-insert-pad ()
502   "Insert a column of pads."
503   (interactive)
504   (save-excursion
505     (let ((se (dna-column-select-func)))
506       (string-rectangle (car se) (cdr se) dna-column-pad))))
507
508 (defun dna-column-delete ()
509   "Delete the current column of dna."
510   (interactive)
511   (save-excursion
512     (let ((se (dna-column-select-func)))
513       (kill-rectangle (car se) (cdr se)))))
514
515 ;;; Per base colors
516
517 (defun dna-base-color-make-faces (&optional force)
518   "Build a face to display bases with.  FORCE remakes the faces."
519   (when (or (not (facep 'dna-face-t)) force)
520     (let ((base-list '("a" "c" "g" "t"))
521           base base-face)
522       (while base-list
523         (setq base (car base-list))
524         (setq base-face (intern (concat "dna-base-face-" base)))
525         (make-face base-face)
526         (set-face-foreground
527          base-face (symbol-value (intern (concat "dna-base-color-" base))))
528         (setq base-list (cdr base-list))))))
529
530 ;; Make faces on load
531 (dna-base-color-make-faces t)
532
533 (defvar dna-color-bases-auto t
534   "Automaticly deactivate `font-lock-mode' when `dna-color-bases' is run.
535 See dna-color-bases for details.")
536 ;; (setq dna-color-bases-auto t)
537
538 (defun dna-color-bases-region (s e)
539   "Color the bases in the region S to E.
540 NOTE: The function `font-lock-mode' will undo the work of this
541 function if activated.  Disable it before using this
542 function.  If `dna-color-bases-auto' is set then `font-lock-mode'
543 is deactivated automatically."
544   (interactive "r")
545   (if (and dna-color-bases-auto font-lock-mode)
546     (font-lock-mode -1))
547   (if font-lock-mode
548     (error "Font-lock-mode is on -- deactivate it"))
549   (save-excursion
550     (let (c)
551       (goto-char s)
552       (while (< s e)
553         (setq c (downcase (char-after s)))
554         (cond
555          ((eq c ?a)
556           (set-text-properties s (+ s 1) '(face dna-base-face-a)))
557          ((eq c ?c)              (+ s 1)
558           (set-text-properties s (+ s 1) '(face dna-base-face-c)))
559          ((eq c ?g)              (+ s 1)
560           (set-text-properties s (+ s 1) '(face dna-base-face-g)))
561          ((eq c ?t)              (+ s 1)
562           (set-text-properties s (+ s 1) '(face dna-base-face-t)))
563          (t nil))
564         (setq s (+ s 1))))))
565
566 (defun dna-uncolor-bases-region (s e)
567   "Uncolor the bases from S to E."
568   (interactive "r")
569   (remove-text-properties s e '(face nil)))
570
571 ;;; Functions for me.
572
573 ;; I like to datestamp sequences I work with.
574 (defvar dna-timestamp-format "%Y%m%d"
575   "Format of the time stamp which `dna-timestamp-seq' uses.")
576
577 (defun dna-timestamp-seq ()
578   "Insert the current date into the sequence.
579 Assumes fasta format."
580   (interactive)
581   (end-of-line)
582   (dna-beginning-of-sequence)
583   (end-of-line)
584   (insert "   " (format-time-string dna-timestamp-format (current-time))))
585
586 ;; done loading
587 (run-hooks 'dna-mode-load-hook)
588 (provide 'dna-mode)
589
590 ;;; dna-mode.el ends here