comparison .elisp/textmate.el @ 141:e30655eb7050

textmate.el: synced with upstream Includes discovering that ido-imenu was a better textmate-goto-symbol and support for excluding files from grepping that are otherwise tracked in VCS. Useful for symlinks to large trees, and other such things.
author Augie Fackler <durin42@gmail.com>
date Tue, 15 Sep 2009 20:28:22 -0400
parents fe9d358b9fe8
children a24d5587386f 95b7dc384677
comparison
equal deleted inserted replaced
140:7fa84e297c84 141:e30655eb7050
1 ;; textmate.el --- TextMate minor mode for Emacs 1 ;; textmate.el --- TextMate minor mode for Emacs
2 2
3 ;; Copyright (C) 2008 Chris Wanstrath <chris@ozmm.org> 3 ;; Copyright (C) 2008 Chris Wanstrath <chris@ozmm.org> and others
4 4
5 ;; Licensed under the same terms as Emacs. 5 ;; Licensed under the same terms as Emacs.
6 6
7 ;; Version: 0.1.0 7 ;; Version: 0.1.0
8 ;; Keywords: textmate osx mac 8 ;; Keywords: textmate osx mac
9 ;; Created: 22 Nov 2008 9 ;; Created: 22 Nov 2008
10 ;; Author: Chris Wanstrath <chris@ozmm.org> 10 ;; Author: Chris Wanstrath <chris@ozmm.org> and others
11 11
12 ;; This file is NOT part of GNU Emacs. 12 ;; This file is NOT part of GNU Emacs.
13
14 ;; Licensed under the same terms as Emacs.
15 13
16 ;;; Commentary: 14 ;;; Commentary:
17 15
18 ;; This minor mode exists to mimick TextMate's awesome 16 ;; This minor mode exists to mimick TextMate's awesome
19 ;; features. 17 ;; features.
20 18
21 ;; ⌘T - Go to File 19 ;; ⌘T - Go to File
22 ;; ⇧⌘T - Go to Symbol 20 ;; ⇧⌘T - Go to Symbol
23 ;; ⌘L - Go to Line 21 ;; ⌘L - Go to Line
24 ;; ⌘/ - Comment Line (or Selection/Region) 22 ;; ⌘/ - Comment Line (or Selection/Region)
25 ;; ⌘] - Shift Right (currently indents region) 23 ;; ⌘] - Shift Right
26 ;; ⌘[ - Shift Left (not yet implemented) 24 ;; ⌘[ - Shift Left
27 ;; ⌥⌘] - Align Assignments 25 ;; ⌥⌘] - Align Assignments
28 ;; ⌥⌘[ - Indent Line 26 ;; ⌥⌘[ - Indent Line
29 ;; ⌘RET - Insert Newline at Line's End 27 ;; ⌘RET - Insert Newline at Line's End
30 ;; ⌥⌘T - Reset File Cache (for Go to File, cache unused if using git/hg root) 28 ;; ⌥⌘T - Reset File Cache (for Go to File, cache unused if using git/hg root,
29 ;; but resets cached root location, useful if roots
30 ;; are nested)
31 31
32 ;; A "project" in textmate-mode is determined by the presence of 32 ;; A "project" in textmate-mode is determined by the presence of
33 ;; a .git directory. If no .git directory is found in your current 33 ;; a .git directory. If no .git directory is found in your current
34 ;; directory, textmate-mode will traverse upwards until one (or none) 34 ;; directory, textmate-mode will traverse upwards until one (or none)
35 ;; is found. The directory housing the .git directory is presumed 35 ;; is found. The directory housing the .git directory is presumed
52 ;; (textmate-mode) 52 ;; (textmate-mode)
53 53
54 ;;; Depends on imenu 54 ;;; Depends on imenu
55 (require 'imenu) 55 (require 'imenu)
56 56
57 ;;; Needed for flet
58 (eval-when-compile
59 (require 'cl))
60
57 ;;; Minor mode 61 ;;; Minor mode
58 62
59 (defvar textmate-use-file-cache t 63 (defvar textmate-use-file-cache t
60 "* Should `textmate-goto-file' keep a local cache of files?") 64 "* Should `textmate-goto-file' keep a local cache of files?")
61 65
71 `((ido ,(lambda (a) (progn (ido-mode a) (setq ido-enable-flex-matching t)))) 75 `((ido ,(lambda (a) (progn (ido-mode a) (setq ido-enable-flex-matching t))))
72 (icicles ,(lambda (a) (icy-mode a))) 76 (icicles ,(lambda (a) (icy-mode a)))
73 (none ,(lambda (a) ()))) 77 (none ,(lambda (a) ())))
74 "The list of functions to enable and disable completing minor modes") 78 "The list of functions to enable and disable completing minor modes")
75 79
76 (defvar *textmate-mode-map* (make-sparse-keymap)) 80 (defvar *textmate-mode-map*
81 (let ((map (make-sparse-keymap)))
82 (cond ((featurep 'aquamacs)
83 (define-key map [A-return] 'textmate-next-line)
84 (define-key map (kbd "A-M-t") 'textmate-clear-cache)
85 (define-key map (kbd "A-M-]") 'align)
86 (define-key map (kbd "A-M-[") 'indent-according-to-mode)
87 (define-key map (kbd "A-]") 'textmate-shift-right)
88 (define-key map (kbd "A-[") 'textmate-shift-left)
89 (define-key map (kbd "A-/") 'comment-or-uncomment-region-or-line)
90 (define-key map (kbd "A-t") 'textmate-goto-file)
91 (define-key map (kbd "A-T") 'textmate-goto-symbol))
92 ((and (featurep 'mac-carbon) (eq window-system 'mac) mac-key-mode)
93 (define-key map [(alt meta return)] 'textmate-next-line)
94 (define-key map [(alt meta t)] 'textmate-clear-cache)
95 (define-key map [(alt meta \])] 'align)
96 (define-key map [(alt meta \[)] 'indent-according-to-mode)
97 (define-key map [(alt \])] 'textmate-shift-right)
98 (define-key map [(alt \[)] 'textmate-shift-left)
99 (define-key map [(meta /)] 'comment-or-uncomment-region-or-line)
100 (define-key map [(alt t)] 'textmate-goto-file)
101 (define-key map [(alt shift t)] 'textmate-goto-symbol))
102 ((featurep 'ns) ;; Emacs.app
103 (define-key map [(super meta return)] 'textmate-next-line)
104 (define-key map [(super meta t)] 'textmate-clear-cache)
105 (define-key map [(super meta \])] 'align)
106 (define-key map [(super meta \[)] 'indent-according-to-mode)
107 (define-key map [(super \])] 'textmate-shift-right)
108 (define-key map [(super \[)] 'textmate-shift-left)
109 (define-key map [(super /)] 'comment-or-uncomment-region-or-line)
110 (define-key map [(super t)] 'textmate-goto-file)
111 (define-key map [(super shift t)] 'textmate-goto-symbol))
112 (t ;; Any other version
113 (define-key map [(meta return)] 'textmate-next-line)
114 (define-key map [(control c)(control t)] 'textmate-clear-cache)
115 (define-key map [(control c)(control a)] 'align)
116 (define-key map [(control tab)] 'textmate-shift-right)
117 (define-key map [(control shift tab)] 'textmate-shift-left)
118 (define-key map [(control c)(control k)] 'comment-or-uncomment-region-or-line)
119 (define-key map [(meta t)] 'textmate-goto-file)
120 (define-key map [(meta shift t)] 'textmate-goto-symbol)))
121 map))
122
123
77 (defvar *textmate-project-root* nil) 124 (defvar *textmate-project-root* nil)
78 (defvar *textmate-project-files* '()) 125 (defvar *textmate-project-files* '())
126
79 (defvar *textmate-gf-exclude* 127 (defvar *textmate-gf-exclude*
80 "/\\.|vendor|fixtures|tmp|log|build|\\.xcodeproj|\\.nib|\\.framework|\\.app|\\.pbproj|\\.pbxproj|\\.xcode|\\.xcodeproj|\\.bundle") 128 "/\\.|vendor|fixtures|tmp|log|build|\\.xcodeproj|\\.nib|\\.framework|\\.app|\\.pbproj|\\.pbxproj|\\.xcode|\\.xcodeproj|\\.bundle|\\.pyc")
81 129
82 (defvar *textmate-keybindings-list* `((textmate-next-line 130 (defvar *textmate-project-roots*
83 [A-return] [M-return]) 131 '(".git" ".hg" "Rakefile" "Makefile" "README" "build.xml"))
84 (textmate-clear-cache 132
85 ,(kbd "A-M-t") [(control c)(control t)]) 133 (defvar *textmate-vcs-exclude* nil
86 (align 134 "string to give to grep -V to exclude some VCS paths from being grepped."
87 ,(kbd "A-M-]") [(control c)(control a)]) 135 )
88 (indent-according-to-mode
89 ,(kbd "A-M-[") nil)
90 (indent-region
91 ,(kbd "A-]") [(control tab)])
92 (comment-or-uncomment-region-or-line
93 ,(kbd "A-/") [(control c)(control k)])
94 (textmate-goto-file
95 ,(kbd "A-t") [(meta t)])
96 (textmate-goto-symbol
97 ,(kbd "A-T") [(meta T)])
98 (textmate-toggle-camel-case
99 ,(kbd "C-_") [(control _)])))
100
101 (defvar *textmate-project-root-p*
102 #'(lambda (coll) (or (member ".git" coll)
103 (member ".hg" coll)
104 ))
105 "*Lambda that, given a collection of directory entries, returns
106 non-nil if it represents the project root.")
107 136
108 (defvar *textmate-find-in-project-default* nil) 137 (defvar *textmate-find-in-project-default* nil)
138
109 (defvar *textmate-find-in-project-type-default* nil) 139 (defvar *textmate-find-in-project-type-default* nil)
110 140
111 ;;; Bindings 141 ;;; Bindings
112 142
113 (defun textmate-ido-fix () 143 (defun textmate-ido-fix ()
114 "Add up/down keybindings for ido." 144 "Add up/down keybindings for ido."
115 (define-key ido-completion-map [up] 'ido-prev-match) 145 (define-key ido-completion-map [up] 'ido-prev-match)
116 (define-key ido-completion-map [down] 'ido-next-match)) 146 (define-key ido-completion-map [down] 'ido-next-match))
117 147
118 (defun textmate-bind-keys ()
119 (add-hook 'ido-setup-hook 'textmate-ido-fix)
120
121 ; weakness until i figure out how to do this right
122 (when (boundp 'osx-key-mode-map)
123 (define-key osx-key-mode-map (kbd "A-t") 'textmate-goto-file)
124 (define-key osx-key-mode-map (kbd "A-T") 'textmate-goto-symbol))
125
126 (let ((member) (i 0) (access (if (boundp 'aquamacs-version) 'cadr 'caddr)))
127 (setq member (nth i *textmate-keybindings-list*))
128 (while member
129 (if (funcall access member)
130 (define-key *textmate-mode-map* (funcall access member) (car member)))
131 (setq member (nth i *textmate-keybindings-list*))
132 (setq i (+ i 1)))))
133
134 (defun textmate-completing-read (&rest args) 148 (defun textmate-completing-read (&rest args)
135 (let ((reading-fn (cadr (assoc textmate-completing-library *textmate-completing-function-alist*)))) 149 (let ((reading-fn (cadr (assoc textmate-completing-library *textmate-completing-function-alist*))))
136 (apply (symbol-function reading-fn) args))) 150 (apply (symbol-function reading-fn) args)))
137 151
152 ;;; allow-line-as-region-for-function adds an "-or-line" version of
153 ;;; the given comment function which (un)comments the current line is
154 ;;; the mark is not active. This code comes from Aquamac's osxkeys.el
155 ;;; and is licensed under the GPL
156
157 (defmacro allow-line-as-region-for-function (orig-function)
158 `(defun ,(intern (concat (symbol-name orig-function) "-or-line"))
159 ()
160 ,(format "Like `%s', but acts on the current line if mark is not active." orig-function)
161 (interactive)
162 (if mark-active
163 (call-interactively (function ,orig-function))
164 (save-excursion
165 ;; define a region (temporarily) -- so any C-u prefixes etc. are preserved.
166 (beginning-of-line)
167 (set-mark (point))
168 (end-of-line)
169 (call-interactively (function ,orig-function))))))
170
171 (defun textmate-define-comment-line ()
172 "Add or-line (un)comment function if not already defined"
173 (unless (fboundp 'comment-or-uncomment-region-or-line)
174 (allow-line-as-region-for-function comment-or-uncomment-region)))
175
138 ;;; Commands 176 ;;; Commands
139 177
140 (defun textmate-next-line () 178 (defun textmate-next-line ()
141 (interactive) 179 (interactive)
142 (end-of-line) 180 (end-of-line)
143 (newline-and-indent)) 181 (newline-and-indent))
144 182
145 ;; http://chopmo.blogspot.com/2008/09/quickly-jumping-to-symbols.html 183 ;; http://chopmo.blogspot.com/2008/09/quickly-jumping-to-symbols.html
146 (defun textmate-goto-symbol () 184 (defun textmate-goto-symbol ()
147 "Will update the imenu index and then use ido to select a symbol to navigate to" 185 "Update the imenu index and then use ido to select a symbol to navigate to.
186 Symbols matching the text at point are put first in the completion list."
148 (interactive) 187 (interactive)
149 (imenu--make-index-alist) 188 (imenu--make-index-alist)
150 (let ((name-and-pos '()) 189 (let ((name-and-pos '())
151 (symbol-names '())) 190 (symbol-names '()))
152 (flet ((addsymbols (symbol-list) 191 (flet ((addsymbols (symbol-list)
167 206
168 (unless (or (null position) (null name)) 207 (unless (or (null position) (null name))
169 (add-to-list 'symbol-names name) 208 (add-to-list 'symbol-names name)
170 (add-to-list 'name-and-pos (cons name position)))))))) 209 (add-to-list 'name-and-pos (cons name position))))))))
171 (addsymbols imenu--index-alist)) 210 (addsymbols imenu--index-alist))
172 (let* ((selected-symbol (textmate-completing-read "Symbol: " symbol-names)) 211 ;; If there are matching symbols at point, put them at the beginning of `symbol-names'.
212 (let ((symbol-at-point (thing-at-point 'symbol)))
213 (when symbol-at-point
214 (let* ((regexp (concat (regexp-quote symbol-at-point) "$"))
215 (matching-symbols (delq nil (mapcar (lambda (symbol)
216 (if (string-match regexp symbol) symbol))
217 symbol-names))))
218 (when matching-symbols
219 (sort matching-symbols (lambda (a b) (> (length a) (length b))))
220 (mapc (lambda (symbol) (setq symbol-names (cons symbol (delete symbol symbol-names))))
221 matching-symbols)))))
222 (let* ((selected-symbol (ido-completing-read "Symbol? " symbol-names))
173 (position (cdr (assoc selected-symbol name-and-pos)))) 223 (position (cdr (assoc selected-symbol name-and-pos))))
174 (goto-char position)))) 224 (goto-char position))))
175 225
176 (defun textmate-goto-file () 226 (defun textmate-goto-file ()
177 (interactive) 227 (interactive)
178 (let ((root (textmate-project-root))) 228 (let ((root (textmate-project-root)))
179 (when (null root) 229 (when (null root)
180 (error "Can't find any .git directory")) 230 (error
231 (concat
232 "Can't find a sutiable project root ("
233 (string-join " " *textmate-project-roots* )
234 ")")))
181 (find-file 235 (find-file
182 (concat 236 (concat
183 (expand-file-name root) "/" 237 (expand-file-name root) "/"
184 (textmate-completing-read 238 (textmate-completing-read
185 "Find file: " 239 "Find file: "
217 (concat "cd " 271 (concat "cd "
218 root 272 root
219 " ; " 273 " ; "
220 (cond ((string= type "git") "git ls-files") 274 (cond ((string= type "git") "git ls-files")
221 ((string= type "hg") "hg manifest")) 275 ((string= type "hg") "hg manifest"))
276 (if *textmate-vcs-exclude*
277 (concat " | grep -v " (shell-quote-argument *textmate-vcs-exclude*))
278 "")
222 " | xargs grep -nR " 279 " | xargs grep -nR "
223 (if pattern (concat " --include='" pattern "' ") "") 280 (if pattern (concat " --include='" pattern "' ") "")
224 " -- " 281 " -- "
225 (shell-quote-argument re))) 282 (shell-quote-argument re)))
226 (t (concat "cd " root "; egrep -nR --exclude='" 283 (t (concat "cd " root "; egrep -nR --exclude='"
269 (downcase-region start end) 326 (downcase-region start end)
270 ))))) 327 )))))
271 328
272 ;;; Utilities 329 ;;; Utilities
273 330
274 (defun textmate-also-ignore (pattern)
275 "Also ignore PATTERN in project files."
276 (setq *textmate-gf-exclude*
277 (concat *textmate-gf-exclude* "|" pattern)))
278
279 (defun textmate-project-root-type (root) 331 (defun textmate-project-root-type (root)
280 (cond ((member ".git" (directory-files root)) "git") 332 (cond ((member ".git" (directory-files root)) "git")
281 ((member ".hg" (directory-files root)) "hg") 333 ((member ".hg" (directory-files root)) "hg")
282 (t "unknown") 334 (t "unknown")
283 )) 335 ))
321 (if root 373 (if root
322 (setq *textmate-project-root* (expand-file-name (concat root "/"))) 374 (setq *textmate-project-root* (expand-file-name (concat root "/")))
323 (setq *textmate-project-root* nil)))) 375 (setq *textmate-project-root* nil))))
324 *textmate-project-root*) 376 *textmate-project-root*)
325 377
378 (defun root-match(root names)
379 (member (car names) (directory-files root)))
380
381 (defun root-matches(root names)
382 (if (root-match root names)
383 (root-match root names)
384 (if (eq (length (cdr names)) 0)
385 'nil
386 (root-matches root (cdr names))
387 )))
388
326 (defun textmate-find-project-root (&optional root) 389 (defun textmate-find-project-root (&optional root)
327 (when (null root) (setq root default-directory)) 390 (when (null root) (setq root default-directory))
328 (cond 391 (cond
329 ((funcall *textmate-project-root-p* (directory-files root)) 392 ((root-matches root *textmate-project-roots*)
330 (expand-file-name root)) 393 (expand-file-name root))
331 ((equal (expand-file-name root) "/") nil) 394 ((equal (expand-file-name root) "/") nil)
332 (t (textmate-find-project-root (concat (file-name-as-directory root) ".."))))) 395 (t (textmate-find-project-root (concat (file-name-as-directory root) "..")))))
396
397 (defun textmate-shift-right (&optional arg)
398 "Shift the line or region to the ARG places to the right.
399
400 A place is considered `tab-width' character columns."
401 (interactive)
402 (let ((deactivate-mark nil)
403 (beg (or (and mark-active (region-beginning))
404 (line-beginning-position)))
405 (end (or (and mark-active (region-end)) (line-end-position))))
406 (indent-rigidly beg end (* (or arg 1) tab-width))))
407
408 (defun textmate-shift-left (&optional arg)
409 "Shift the line or region to the ARG places to the left."
410 (interactive)
411 (textmate-shift-right (* -1 (or arg 1))))
333 412
334 ;;;###autoload 413 ;;;###autoload
335 (define-minor-mode textmate-mode "TextMate Emulation Minor Mode" 414 (define-minor-mode textmate-mode "TextMate Emulation Minor Mode"
336 :lighter " mate" :global t :keymap *textmate-mode-map* 415 :lighter " mate" :global t :keymap *textmate-mode-map*
337 (textmate-bind-keys) 416 (add-hook 'ido-setup-hook 'textmate-ido-fix)
417 (textmate-define-comment-line)
338 ; activate preferred completion library 418 ; activate preferred completion library
339 (dolist (mode *textmate-completing-minor-mode-alist*) 419 (dolist (mode *textmate-completing-minor-mode-alist*)
340 (if (eq (car mode) textmate-completing-library) 420 (if (eq (car mode) textmate-completing-library)
341 (funcall (cadr mode) t) 421 (funcall (cadr mode) t)
342 (when (fboundp 422 (when (fboundp