分类: 嵌入式
2012-01-19 14:57:46
通常,在 Mac 下开发 Cocoa 应用程序或是 iPhone 应用程序的时候使用的是 XCode ,但是习惯了 Emacs 的人也许已经不习惯这样的 IDE 了。比如本人,自从接触 Emacs 以后,基本上所有的程序,博客,工作报告等都是用它来完成的。这里,我将给大家介绍在 Emacs 下开发 iPhone 应用程序的方法,也许试过之后你也会喜欢上它的(在windows/linux下开发的用户也可以试试,一切都可以自动化的完成,参考这里和这里。
环境设定 设定 XCode 的外部编辑器首先将缺省的编辑器由 XCode 更改为 Emacs。这样一来,双击 XCode 的源文件后,将用 Emacs 打开。
如下图所示,在「环境设定」->「文件类型」->「file」->「text」-> 「sourcecode」-> 「sourcecode.c」->「外部编辑器」-> 「其他」中选择「Emacs.app」。
必须选择「其他」。最初 emacs 由 Terminal 内启动。
这里,工程文件还是由 XCode 打开的。
Emacs 中管理 Objective-C 文件利用 Emacs 开发 Objective-C 语言程序的时候,需要打开 objc-mode。
首先在 ~/.emacs.el 中设定关联 objc 语言的文件后缀名 .m 、 .mm 、.h。
1 2 |
(add-to-list 'auto-mode-alist '("//.mm?$" . objc-mode)) (add-to-list 'auto-mode-alist '("//.h$" . objc-mode)) |
但是,后缀名为 .m 的文件除了 Objective-C 以外,matlab 中也在使用,后缀名为 .h 的文件 C/C++ 中也被应用。如果只是想这样单纯的设置,应该还是会带来一些不便的。不过不要紧,在 Emacs22 以后,为了解决这个问题可以设定magic-mode-alist。它可以解析具体文件中的内容确定具体的mode。
这里,判断文件行头是否有 @implementation 、 @interface 、 @protocol ,如果有,就设定 objc-mode。
1 2 3 |
(add-to-list 'magic-mode-alist '("//(.//|/n//)*/n@implementation" . objc-mode)) (add-to-list 'magic-mode-alist '("//(.//|/n//)*/n@interface" . objc-mode)) (add-to-list 'magic-mode-alist '("//(.//|/n//)*/n@protocol" . objc-mode)) |
这里使用 xcodebuild 命令行实现命令行的编译方式,你也可以使用这里的方法,使用 gcc&Makefile 。
编译可以使用下面的命令:
1 | xcodebuild -configuration Debug -sdk iphonesimulator3.1.2 |
执行可以通过 AppleScript 来实现。
1 2 3 4 5 6 |
tell application "Xcode" to activate tell application "System Events" tell process "Xcode" key code 36 using {command down} end tell end tell |
这里直接使用了 key code 。如果你自定义了 Mac 的 key code 话,就不能正常工作了。这里使用的 key code 的意思如下:
using | 意思 | Unicode | 菜单上的记号 |
---|---|---|---|
command down | 命令键 | 0x2318 | ⌘ |
control down | 控制键 | 0x2303 | ⌃ |
option down | alt键 | 0x2325 | ⌥ |
shift down | shift键 | 0x21E7 | ⇧ |
以及
键 | key code |
---|---|
esc | 53 |
tab | 48 |
space | 49 |
return | 36 |
delete | 51 |
left arrow | 123 |
right arrow | 124 |
down arrow | 125 |
up arrow | 126 |
所以,这里的例子就是 Ctr+return 。然后将该 AppleScript 嵌入到 Emacs Lisp 中。(这里只针对 Carbon Emacs 或Cocoa Emacs 有效)
1 2 3 4 5 6 7 8 9 10 11 12 |
(defun xcode:buildandrun () (interactive) (do-applescript (format (concat "tell application /"Xcode/" to activate /r" "tell application /"System Events/" /r" " tell process /"Xcode/" /r" " key code 36 using {command down} /r" " end tell /r" "end tell /r" )))) |
然后使用 M-x xcode:buildandrun 来执行。或者绑定下面的快捷键。
1 2 3 4 |
(add-hook 'objc-mode-hook (lambda () (define-key objc-mode-map (kbd "C-c C-r") 'xcode:buildandrun) )) |
开发程序的时候经常会用到帮助文档,类似windows下的MSDN。在 Mac 下利用命令行形式检索帮助时用 docsetutil 命令。比如下面的方法:
1 | /Developer/usr/bin/docsetutil search /Developer/Platforms/iPhoneOS.platform/Developer/Documentation/DocSets/com.apple.adc.documentation.AppleiPhone3_1.iPhoneLibrary.docset -query 'word' |
Emacs 中利用这一命令,可以使用 xcode-document-viewer.el 。运行的时候需要 。可以在 下载 w3m,按照下面的方法安装。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
curl -O tar xvfz gc.tar.gz cd gc ./configure make sudo make install cd .. tar xvfz w3m-0.5.2.tar.gz ./configure make sudo make install |
这之后,安装 emacs-w3m 到 .emacs.d/lisp 下。
1 2 3 4 5 6 7 |
cvs -d :pserver:anonymous@cvs.namazu.org:/storage/cvsroot co emacs-w3m cd emacs-w3m autoconf ./configure --with-lispdir=~/.emacs.d/lisp/w3m --datarootdir=~/.emacs.d/share --with-icondir=~/.emacs.d/share/icon make make install make install-icons |
1 2 3 4 5 |
cd ~/.emacs.d/lisp curl -O curl -O http://github.com/sakito/emacs-xcode-document-viewer/raw/master/xcode-document-viewer.el # 这里是原始版 # curl -O http://github.com/imakado/emacs-xcode-document-viewer/raw/master/xcode-document-viewer.el |
然后在 .emacs.el 中像下面一样设置。
1 2 3 4 5 6 7 8 9 |
;; 自动加载 emacs-w3m (autoload 'w3m "w3m" "Interface for w3m on Emacs." t) (require 'xcode-document-viewer) (setq xcdoc:document-path "/Developer/Platforms/iPhoneOS.platform/Developer/Documentation/DocSets/com.apple.adc.documentation.AppleiPhone3_1.iPhoneLibrary.docset") (setq xcdoc:open-w3m-other-buffer t) (add-hook 'objc-mode-hook (lambda () ;; 用 C-c w 来检索文档 (define-key objc-mode-map (kbd "C-c w") 'xcdoc:ask-search))) |
比如像打开 #import
快捷键是 C-x C-f ,在光标处的头文件执行它,将打开对应的头文件。
1 2 3 4 5 6 7 8 |
(ffap-bindings) ;; 设定搜索的路径 ffap-c-path ;; (setq ffap-c-path ;; '("/usr/include" "/usr/local/include")) ;; 如果是新文件要确认 (setq ffap-newfile-prompt t) ;; ffap-kpathsea-expand-path 展开路径的深度 (setq ffap-kpathsea-depth 5) |
另外,由 .h 文件切换到 .m 文件、或者由 .m 文件切换到对应的 .h 文件、可以使用 ff-find-other-file。
如下设置,使用 C-c o 来切换文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
(setq ff-other-file-alist '(("//.mm?$" (".h")) ("//.cc$" (".hh" ".h")) ("//.hh$" (".cc" ".C")) ("//.c$" (".h")) ("//.h$" (".c" ".cc" ".C" ".CC" ".cxx" ".cpp" ".m" ".mm")) ("//.C$" (".H" ".hh" ".h")) ("//.H$" (".C" ".CC")) ("//.CC$" (".HH" ".H" ".hh" ".h")) ("//.HH$" (".CC")) ("//.cxx$" (".hh" ".h")) ("//.cpp$" (".hpp" ".hh" ".h")) ("//.hpp$" (".cpp" ".c")))) (add-hook 'objc-mode-hook (lambda () (define-key c-mode-base-map (kbd "C-c o") 'ff-find-other-file) )) |
在 Emacs 中也能完成 Objective-C 的补全功能。设立,我们使用 auto-complete 、 company-mode 、 ac-company。
安装
1 2 3 4 5 6 7 8 9 10 |
cd ~/.emacs.d mkdir lisp cd lisp curl -O curl -O curl -O curl -O curl -O curl -O tar xvfj company-0.4.3.tar.bz2 |
在 .emacs.el 中添加下面的设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
;; load-path 路径 (let ((default-directory (expand-file-name "~/.emacs.d/lisp"))) (add-to-list 'load-path default-directory) (if (fboundp 'normal-top-level-add-subdirs-to-load-path) (normal-top-level-add-subdirs-to-load-path))) ;; 加载 (require 'auto-complete) (require 'auto-complete-config) (require 'ac-company) (global-auto-complete-mode t) ;; ac-company 中设置 company-xcode 有效 (ac-company-define-source ac-source-company-xcode company-xcode) ;; 设定 objc-mode 中补全 ac-mode (setq ac-modes (append ac-modes '(objc-mode))) ;; hook (add-hook 'objc-mode-hook (lambda () (define-key objc-mode-map (kbd "/t") 'ac-complete) ;; 使用 XCode 的补全功能有效 (push 'ac-source-company-xcode ac-sources) ;; C++ 关键词补全 (push 'ac-source-c++-keywords ac-sources) )) ;; 补全窗口中的热键 (define-key ac-completing-map (kbd "C-n") 'ac-next) (define-key ac-completing-map (kbd "C-p") 'ac-previous) (define-key ac-completing-map (kbd "M-/") 'ac-stop) ;; 是否自动启动补全功能 (setq ac-auto-start nil) ;; 启动热键 (ac-set-trigger-key "TAB") ;; 候補的最大件数(缺省 10件) (setq ac-candidate-max 20) |
如果不能很好的完成补全,先用 XCode 编译一次源代码,然后再试应该没有什么问题了。因为上记补全的方法实际上是使用了 XCode 的 xcodeindex 命令,需要动态地收集补全的信息。
etags如果你不喜欢这种方式,还可以试试 etags(或者 gtags,这里只介绍 etags,有兴趣的朋友可以自己试试 gtags )。它主要是利用了源代码文件(类名,函数名等)来建立索引(tag)。
首先,使用 etags 命令生成tag文件。以下的例子生成 tag 到 ~/.emacs.d/share/tags 下。
1 2 3 4 |
cd ~/.emacs.d mkdir -p share/tags cd share/tags find /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS${VER}.sdk/System/Library/Frameworks -name "*.h" | xargs etags -f objc.TAGS -l objc |
生成的文件名为 objc.TAGS ,内部只是类的名称。如果要得到比较详细的信息(函数名等)使用下面的shell脚本。
1 2 3 4 5 6 7 8 9 10 11 |
#!/bin/sh s="/t " S="[$s]*" w="_a-zA-Z0-9" CN="[A-Z][$w]*" NM="[$w][$w]*" SDK="/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS${VER}.sdk/System/Library/Frameworks" find $SDK -name "*.h" | xargs etags -a --declarations -r "/$S[-+]$S(/($S$NM/)/{1,3/}$S/**$S)?$S/($NM/)$S[:;]//2/" -f frm.tags sed "/^@class/d" frm.tags > objc.TAGS |
tag 文件比较大,这里我们只是作为 objc-mode 的补全候补来使用,这里使用到了 etags-table.el。
安装
1 2 |
cd ~/.emacs.d/lisp curl -O |
在 .emacs.el 文件中添加下面的设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
;; etags-table 有效 (require 'etags-table) (add-to-list 'etags-table-alist '("//.[mh]$" "~/.emacs.d/share/tags/objc.TAGS")) ;; auto-complete 中待确认的 etags 参数 ;; 3文字以上时,补全功能有效 (defvar ac-source-etags '((candidates . (lambda () (all-completions ac-target (tags-completion-table)))) (candidate-face . ac-candidate-face) (selection-face . ac-selection-face) (requires . 3)) "etags source") (add-hook 'objc-mode-hook (lambda () (push 'ac-source-etags ac-sources))) |
另外,使用 etags 除了补全功能以外,还可以在代码间跳转。使用“M+.”跳到光标处源代码位置(比如函数定义处),使用“Alt+*”还可以跳回来。
Text macros(模板)XCode 中有一个名为「Text macros」的功能,使用它可以自动生成模板代码,提高了开发的效率,Emacs 中 YASnippet 就可以实现同样的功能。
安装
1 2 3 4 |
cd ~/.emacs.d/lisp curl -O tar xvfj yasnippet-0.6.1c.tar.bz2 cd yasnippet-0.6.1c |
在 .emacs.el 文件中添加下面的设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
(let ((default-directory (expand-file-name "~/.emacs.d/lisp"))) (add-to-list 'load-path default-directory) (if (fboundp 'normal-top-level-add-subdirs-to-load-path) (normal-top-level-add-subdirs-to-load-path))) (require 'yasnippet) ;; 设置snippet的位置 (setq yas/root-directory "~/.emacs.d/lisp/yasnippet-0.6.1c/snippets") ;; 不要菜单 (setq yas/use-menu nil) ;; 初始化 (yas/initialize) (yas/load-directory yas/root-directory) |
这样一来,在 objc-mode 中按下Tab键、就可以启动 YASnippet 了。你可以输入 for 按后点击tab试试。
但是 YASnippet 中供 objc-mode 使用的 snippets 并不是很多、利用 XCode 的 TextMacro 可以解决这个问题。
XCode 的 TextMacros 位于下面的位置,因为是文本文件,你可以用 [[Emacs]] 打开来查看。首先添加新的目录用来保存 snippets。这里我们创建 ~/.emacs.d/etc/snippets 目录,然后在 .emacs.el 文件中设置:
1 2 3 4 |
;; 设置复数的 snippets 路径 (setq yas/root-directory '("~/.emacs.d/lisp/yasnippet-0.6.1c/snippets" "~/.emacs.d/etc/snippets")) (mapc 'yas/load-directory yas/root-directory) |
在 ~/.emacs.d/etc/snippets 目录下创建 objc-mode 子目录、在其下创建后缀名为 .yasnippet 的模板文件。比如像下面 try.yasnippet 的文件。
1 2 3 4 5 6 7 8 9 10 11 12 |
# -*- mode: snippet -*- #name : @try { ... } @catch { ... } @finally { ... } # -- @try { $1 } @catch (N***ception * e) { $2 } @finally { $3 }$0 |
你可以参考 XCode 中的 TextMacros 实现所需的模板。
自动插入匹配的括号Emacs 中标准的括号自动插入功能如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
(add-hook 'c-mode-common-hook '(lambda() ;; 插入对称的括号 (make-variable-buffer-local 'skeleton-pair) (make-variable-buffer-local 'skeleton-pair-on-word) (setq skeleton-pair-on-word t) (setq skeleton-pair t) (make-variable-buffer-local 'skeleton-pair-alist) (local-set-key (kbd "(") 'skeleton-pair-insert-maybe) (local-set-key (kbd "[") 'skeleton-pair-insert-maybe) (local-set-key (kbd "{") 'skeleton-pair-insert-maybe) (local-set-key (kbd "`") 'skeleton-pair-insert-maybe) (local-set-key (kbd "/"") 'skeleton-pair-insert-maybe) )) |
你也可以使用 ,也许能更方便一些。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
(defun ik:insert-eol (s) (interactive) (lexical-let ((s s)) (smartchr-make-struct :insert-fn (lambda () (save-excursion (goto-char (point-at-eol)) (when (not (string= (char-to-string (preceding-char)) s)) (insert s)))) :cleanup-fn (lambda () (save-excursion (goto-char (point-at-eol)) (delete-backward-char (length s))))))) (defun ik:insert-semicolon-eol () (ik:insert-eol ";")) (defun smartchr-custom-keybindings () (local-set-key (kbd "=") (smartchr '(" = " " == " "="))) (local-set-key (kbd "(") (smartchr '("(`!!')" "("))) (local-set-key (kbd "[") (smartchr '("[`!!']" "[ [`!!'] ]" "["))) (local-set-key (kbd "{") (smartchr '("{/n`!!'/n}" "{`!!'}" "{"))) (local-set-key (kbd "`") (smartchr '("/``!!''" "/`"))) (local-set-key (kbd "/"") (smartchr '("/"`!!'/"" "/""))) (local-set-key (kbd ">") (smartchr '(">" " => " " => '`!!''" " => /"`!!'/""))) (lobal-set-key (kbd "F") (smartchr '("F" "$" "$_" "$_->" "@$"))) (lobal-set-key (kbd "j") (smartchr '("j" ik:insert-semicolon-eol))) ) (defun smartchr-custom-keybindings-objc () (local-set-key (kbd "@") (smartchr '("@/"`!!'/"" "@"))) ) (add-hook 'c-mode-common-hook 'smartchr-custom-keybindings) (add-hook 'objc-mode-hook 'smartchr-custom-keybindings-objc) |
当你按下一位「==」键时,自动输出「 = 」,前后自动添加空白。当再一次输入「=」时,得到的是「 == 」。另外也可以匹配地输入带改行的文字,自定义各种特定的输入等。(比如这里,2回按下“j”键后,自动在改行末尾添加“;”)。
缩进将下面的设定添加到 .emacs.el 中,使用tab的距离为4个空白位。
1 2 3 4 5 6 |
(add-hook 'c-mode-common-hook '(lambda() (c-set-style "cc-mode"))) (setq-default indent-tabs-mode nil) (setq-default tab-width 4) |
使用 cua-mode 可以方便地实现代码中的矩形选择。将下面的代码添加到 .emacs.el 中,使用 C-RET 可以进入矩形选择模式。
1 2 |
(setq cua-enable-cua-keys nil) (cua-mode t) |
可以使用 flymake 来完成语法检查。在 .emacs.el 中添加下面的设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
(require 'flymake) (defvar xcode:gccver "4.0") (defvar xcode:sdkver "3.1.2") (defvar xcode:sdkpath "/Developer/Platforms/iPhoneSimulator.platform/Developer") (defvar xcode:sdk (concat xcode:sdkpath "/SDKs/iPhoneSimulator" xcode:sdkver ".sdk")) (defvar flymake-objc-compiler (concat xcode:sdkpath "/usr/bin/gcc-" xcode:gccver)) (defvar flymake-objc-compile-default-options (list "-Wall" "-Wextra" "-fsyntax-only" "-ObjC" "-std=c99" "-isysroot" xcode:sdk)) (defvar flymake-last-position nil) (defvar flymake-objc-compile-options '("-I.")) (defun flymake-objc-init () (let* ((temp-file (flymake-init-create-temp-buffer-copy 'flymake-create-temp-inplace)) (local-file (file-relative-name temp-file (file-name-directory buffer-file-name)))) (list flymake-objc-compiler (append flymake-objc-compile-default-options flymake-objc-compile-options (list local-file))))) (add-hook 'objc-mode-hook (lambda () (push '("//.m$" flymake-objc-init) flymake-allowed-file-name-masks) (push '("//.h$" flymake-objc-init) flymake-allowed-file-name-masks) (if (and (not (null buffer-file-name)) (file-writable-p buffer-file-name)) (flymake-mode t)) )) |
如果检查到有语法错误,有错误的代码行自动显示到 minibuffer 中,如下设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
(defun flymake-display-err-minibuffer () "改行有 error 或 warinig 显示在 minibuffer" (interactive) (let* ((line-no (flymake-current-line-no)) (line-err-info-list (nth 0 (flymake-find-err-info flymake-err-info line-no))) (count (length line-err-info-list))) (while (> count 0) (when line-err-info-list (let* ((file (flymake-ler-file (nth (1- count) line-err-info-list))) (full-file (flymake-ler-full-file (nth (1- count) line-err-info-list))) (text (flymake-ler-text (nth (1- count) line-err-info-list))) (line (flymake-ler-line (nth (1- count) line-err-info-list)))) (message "[%s] %s" line text))) (setq count (1- count))))) (defadvice flymake-goto-next-error (after display-message activate compile) "下一个错误" (flymake-display-err-minibuffer)) (defadvice flymake-goto-prev-error (after display-message activate compile) "前一个错误" (flymake-display-err-minibuffer)) (defadvice flymake-mode (before post-command-stuff activate compile) "为了将问题行自动显示到 minibuffer 中,添加 post command hook " (set (make-local-variable 'post-command-hook) (add-hook 'post-command-hook 'flymake-display-err-minibuffer))) ;; post-command-hook 与 anything.el 有冲突时使用 (define-key global-map (kbd "C-c d") 'flymake-display-err-minibuffer) |
你可以把 flymake-goto-next-error 与 flymake-goto-prev-error 分配到自己喜欢的快捷键上。另外像下面给错误附上颜色,便于区分。
1 2 |
(set-face-background 'flymake-errline "red") (set-face-background 'flymake-warnline "yellow") |
用过 Emacs 的朋友也许都知道, 随着功能模块的增多,Emacs 的启动速度是越来越慢。这里我们介绍一种加速的方法 — 将 Emacs Lisp 编译为2进制文件。以加快其启动速度。
1 2 3 |
emacs -batch -f batch-byte-compile *.el # Emacs.app /Applications/Emacs.app/Contents/MacOS/Emacs -batch -f batch-byte-compile *.el |