22
|
1 ;; textmate.el --- TextMate minor mode for Emacs |
|
2 |
|
3 ;; Copyright (C) 2008 Chris Wanstrath <chris@ozmm.org> |
|
4 |
|
5 ;; Licensed under the same terms as Emacs. |
|
6 |
|
7 ;; Version: 0.1.0 |
|
8 ;; Keywords: textmate osx mac |
|
9 ;; Created: 22 Nov 2008 |
|
10 ;; Author: Chris Wanstrath <chris@ozmm.org> |
|
11 |
|
12 ;; This file is NOT part of GNU Emacs. |
|
13 |
|
14 ;; Licensed under the same terms as Emacs. |
|
15 |
|
16 ;;; Commentary: |
|
17 |
|
18 ;; This minor mode exists to mimick TextMate's awesome |
|
19 ;; features. |
|
20 |
|
21 ;; ⌘T - Go to File |
|
22 ;; ⇧⌘T - Go to Symbol |
|
23 ;; ⌘L - Go to Line |
|
24 ;; ⌘/ - Comment Line (or Selection/Region) |
|
25 ;; ⌘] - Shift Right (currently indents region) |
|
26 ;; ⌘[ - Shift Left (not yet implemented) |
|
27 ;; ⌥⌘] - Align Assignments |
|
28 ;; ⌥⌘[ - Indent Line |
|
29 ;; ⌘RET - Insert Newline at Line's End |
|
30 ;; ⌥⌘T - Reset File Cache (for Go to File) |
|
31 |
|
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 |
|
34 ;; directory, textmate-mode will traverse upwards until one (or none) |
|
35 ;; is found. The directory housing the .git directory is presumed |
|
36 ;; to be the project's root. |
|
37 |
|
38 ;; In other words, calling Go to File from |
|
39 ;; ~/Projects/fieldrunners/app/views/towers/show.html.erb will use |
|
40 ;; ~/Projects/fieldrunners/ as the root if ~/Projects/fieldrunners/.git |
|
41 ;; exists. |
|
42 |
|
43 ;;; Installation |
|
44 |
|
45 ;; $ cd ~/.emacs.d/vendor |
|
46 ;; $ git clone git://github.com/defunkt/textmate.el.git |
|
47 ;; |
|
48 ;; In your emacs config: |
|
49 ;; |
|
50 ;; (add-to-list 'load-path "~/.emacs.d/vendor/textmate.el") |
|
51 ;; (require 'textmate) |
|
52 ;; (textmate-mode) |
|
53 |
|
54 ;;; Depends on imenu |
|
55 (require 'imenu) |
|
56 |
|
57 ;;; Minor mode |
|
58 |
|
59 (defvar textmate-use-file-cache t |
|
60 "* Should `textmate-goto-file' keep a local cache of files?") |
|
61 |
|
62 (defvar textmate-completing-library 'ido |
|
63 "The library `textmade-goto-symbol' and `textmate-goto-file' should use for completing filenames and symbols (`ido' by default)") |
|
64 |
|
65 (defvar *textmate-completing-function-alist* '((ido ido-completing-read) |
|
66 (icicles icicle-completing-read) |
|
67 (none completing-read)) |
|
68 "The function to call to read file names and symbols from the user") |
|
69 |
|
70 (defvar *textmate-completing-minor-mode-alist* |
|
71 `((ido ,(lambda (a) (progn (ido-mode a) (setq ido-enable-flex-matching t)))) |
|
72 (icicles ,(lambda (a) (icy-mode a))) |
|
73 (none ,(lambda (a) ()))) |
|
74 "The list of functions to enable and disable completing minor modes") |
|
75 |
|
76 (defvar *textmate-mode-map* (make-sparse-keymap)) |
|
77 (defvar *textmate-project-root* nil) |
|
78 (defvar *textmate-project-files* '()) |
|
79 (defvar *textmate-gf-exclude* |
|
80 "/\\.|vendor|fixtures|tmp|log|build|\\.xcodeproj|\\.nib|\\.framework|\\.app|\\.pbproj|\\.pbxproj|\\.xcode|\\.xcodeproj|\\.bundle") |
|
81 |
|
82 (defvar *textmate-keybindings-list* `((textmate-next-line |
|
83 [A-return] [M-return]) |
|
84 (textmate-clear-cache |
|
85 ,(kbd "A-M-t") [(control c)(control t)]) |
|
86 (align |
|
87 ,(kbd "A-M-]") [(control c)(control a)]) |
|
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 |
|
99 (defvar *textmate-project-root-p* |
|
100 #'(lambda (coll) (or (member ".git" coll) |
|
101 (member ".hg" coll) |
|
102 )) |
|
103 "*Lambda that, given a collection of directory entries, returns |
|
104 non-nil if it represents the project root.") |
|
105 |
|
106 ;;; Bindings |
|
107 |
|
108 (defun textmate-ido-fix () |
|
109 "Add up/down keybindings for ido." |
|
110 (define-key ido-completion-map [up] 'ido-prev-match) |
|
111 (define-key ido-completion-map [down] 'ido-next-match)) |
|
112 |
|
113 (defun textmate-bind-keys () |
|
114 (add-hook 'ido-setup-hook 'textmate-ido-fix) |
|
115 |
|
116 ; weakness until i figure out how to do this right |
|
117 (when (boundp 'osx-key-mode-map) |
|
118 (define-key osx-key-mode-map (kbd "A-t") 'textmate-goto-file) |
|
119 (define-key osx-key-mode-map (kbd "A-T") 'textmate-goto-symbol)) |
|
120 |
|
121 (let ((member) (i 0) (access (if (boundp 'aquamacs-version) 'cadr 'caddr))) |
|
122 (setq member (nth i *textmate-keybindings-list*)) |
|
123 (while member |
|
124 (if (funcall access member) |
|
125 (define-key *textmate-mode-map* (funcall access member) (car member))) |
|
126 (setq member (nth i *textmate-keybindings-list*)) |
|
127 (setq i (+ i 1))))) |
|
128 |
|
129 (defun textmate-completing-read (&rest args) |
|
130 (let ((reading-fn (cadr (assoc textmate-completing-library *textmate-completing-function-alist*)))) |
|
131 (apply (symbol-function reading-fn) args))) |
|
132 |
|
133 ;;; Commands |
|
134 |
|
135 (defun textmate-next-line () |
|
136 (interactive) |
|
137 (end-of-line) |
|
138 (newline-and-indent)) |
|
139 |
|
140 ;; http://chopmo.blogspot.com/2008/09/quickly-jumping-to-symbols.html |
|
141 (defun textmate-goto-symbol () |
|
142 "Will update the imenu index and then use ido to select a symbol to navigate to" |
|
143 (interactive) |
|
144 (imenu--make-index-alist) |
|
145 (let ((name-and-pos '()) |
|
146 (symbol-names '())) |
|
147 (flet ((addsymbols (symbol-list) |
|
148 (when (listp symbol-list) |
|
149 (dolist (symbol symbol-list) |
|
150 (let ((name nil) (position nil)) |
|
151 (cond |
|
152 ((and (listp symbol) (imenu--subalist-p symbol)) |
|
153 (addsymbols symbol)) |
|
154 |
|
155 ((listp symbol) |
|
156 (setq name (car symbol)) |
|
157 (setq position (cdr symbol))) |
|
158 |
|
159 ((stringp symbol) |
|
160 (setq name symbol) |
|
161 (setq position (get-text-property 1 'org-imenu-marker symbol)))) |
|
162 |
|
163 (unless (or (null position) (null name)) |
|
164 (add-to-list 'symbol-names name) |
|
165 (add-to-list 'name-and-pos (cons name position)))))))) |
|
166 (addsymbols imenu--index-alist)) |
|
167 (let* ((selected-symbol (textmate-completing-read "Symbol: " symbol-names)) |
|
168 (position (cdr (assoc selected-symbol name-and-pos)))) |
|
169 (goto-char position)))) |
|
170 |
|
171 (defun textmate-goto-file () |
|
172 (interactive) |
|
173 (let ((root (textmate-project-root))) |
|
174 (when (null root) |
|
175 (error "Can't find any .git directory")) |
|
176 (find-file |
|
177 (concat |
|
178 (expand-file-name root) "/" |
|
179 (textmate-completing-read |
|
180 "Find file: " |
|
181 (textmate-cached-project-files root)))))) |
|
182 |
|
183 (defun textmate-clear-cache () |
|
184 (interactive) |
|
185 (setq *textmate-project-root* nil) |
|
186 (setq *textmate-project-files* nil) |
|
187 (message "textmate-mode cache cleared.")) |
|
188 |
|
189 ;;; Utilities |
|
190 |
|
191 (defun textmate-also-ignore (pattern) |
|
192 "Also ignore PATTERN in project files." |
|
193 (setq *textmate-gf-exclude* |
|
194 (concat *textmate-gf-exclude* "|" pattern))) |
|
195 |
|
196 (defun textmate-project-files (root) |
|
197 (split-string |
|
198 (shell-command-to-string |
|
199 (concat |
|
200 "find " |
|
201 root |
|
202 " -type f | grep -vE '" |
|
203 *textmate-gf-exclude* |
|
204 "' | sed 's:" |
|
205 *textmate-project-root* |
|
206 "/::'")) "\n" t)) |
|
207 |
|
208 (defun textmate-cached-project-files (&optional root) |
|
209 (cond |
|
210 ((null textmate-use-file-cache) (textmate-project-files root)) |
|
211 ((equal (textmate-project-root) (car *textmate-project-files*)) |
|
212 (cdr *textmate-project-files*)) |
|
213 (t (cdr (setq *textmate-project-files* |
|
214 `(,root . ,(textmate-project-files root))))))) |
|
215 |
|
216 (defun textmate-project-root () |
|
217 (when (or |
|
218 (null *textmate-project-root*) |
|
219 (not (string-match *textmate-project-root* default-directory))) |
|
220 (let ((root (textmate-find-project-root))) |
|
221 (if root |
|
222 (setq *textmate-project-root* (expand-file-name (concat root "/"))) |
|
223 (setq *textmate-project-root* nil)))) |
|
224 *textmate-project-root*) |
|
225 |
|
226 (defun textmate-find-project-root (&optional root) |
|
227 (when (null root) (setq root default-directory)) |
|
228 (cond |
|
229 ((funcall *textmate-project-root-p* (directory-files root)) |
|
230 (expand-file-name root)) |
|
231 ((equal (expand-file-name root) "/") nil) |
|
232 (t (textmate-find-project-root (concat (file-name-as-directory root) ".."))))) |
|
233 |
|
234 ;;;###autoload |
|
235 (define-minor-mode textmate-mode "TextMate Emulation Minor Mode" |
|
236 :lighter " mate" :global t :keymap *textmate-mode-map* |
|
237 (textmate-bind-keys) |
|
238 ; activate preferred completion library |
|
239 (dolist (mode *textmate-completing-minor-mode-alist*) |
|
240 (if (eq (car mode) textmate-completing-library) |
|
241 (funcall (cadr mode) t) |
|
242 (when (fboundp |
|
243 (cadr (assoc (car mode) *textmate-completing-function-alist*))) |
|
244 (funcall (cadr mode) -1))))) |
|
245 |
|
246 (provide 'textmate) |
|
247 ;;; textmate.el ends here |