文本编辑器是程序员日常工作生活中不可缺少的重要工具。优秀的文本编辑器可以解放劳动力、提高生产率。在程序员的世界里效率是一件重要的事情。因此,文本编辑器之争也就成为大家茶余饭后的谈资(如同程序设计语言一样)。我向来是一个自由主义者。谁好谁坏这种事情,所持的观点和角度不同,有时候很难分辨。程序员和文本编辑器之间的关系,应该有点像谈恋爱,萝卜青菜各有所爱。
Emacs是编辑器排行榜中争论较为突出的一位,支持者声称Emacs是世界上最好用的编辑器(因为配置和扩展的能力实在太强,它确实不仅仅是个文本编辑器,还是IDE、日历/日程/约会管理器、邮件客户端、Web浏览器、命令终端、图片浏览、音乐播放、视频编辑等,甚至曾有人把它比成操作系统.....)。反对者也能列举它诸多不是,将其与神经病、抓狂相联系(比如太多的快捷键绑定把手指头都搞抽筋,比如太古老,比如配置起来太复杂,比如启动的速度可以赶超操作系统等等)。其实采纳两方观点,才能把Emacs看得更清楚。扯远了,回到正题。
话说我和Emacs之间的过往,不严格的说从大学时代就已经开始。那个时候仅仅以为它是古董又蹩脚的东西,21世纪的“新新软件工程师”还有谁会使用它。所以是只闻其名,未见其身。大学毕业工作三年后,一个偶然的机会,我踏进了Emacs的世界(一是因为踏进了lisp世界,二是出于对古董级物品难以遏制的好奇心。)。了解了lisp之后,发现原来Emacs的配置和开发并不是很恐怖,也体会到这个所谓的无所不能的神器的震撼,“Emacs只不过是伪装成编辑器的操作系统”这句话真的一点也不假。
古董级的东西还能活在现今的世界中,一定有它的原因。“Emacs is a refugee from the long dead culture of Lisp Machines”
以上扯的更远了,再回到正题。
既然本篇的题为神器的养成,那么还是要谈谈我在使用Emacs后的一些心得(这才是正题)。对于像我这样用惯了新一代编辑器(主要在微软平台上)的“新新软件工程师”,脑海中形成了固化的概念,傻子式、所见即所得、图形界面形式的编辑器很好用很给力。这个概念说的通用些“因为学习曲线短所以认为很好用很给力”。但如果把这样的概念应用到Emacs上,不合适!Emacs是疯子式(天才式)、所按即所得、提供复杂编辑功能的编辑器。Emacs的概念说的通用些“它不想你一来就上手。学习曲线必须是相当的陡峭。开始使用的时候一点力也不给你。但它关心的是你学成之后,无所不能。”
如果把学习和使用编辑器比作登山。那普通的编辑器不过是个丘陵,你登的容易登不高。Emacs是珠穆朗玛峰,一般人不轻易去登它,二般人只有两个结果,要么登顶要么牺牲。
想要登珠峰,除了练就一身好体魄,还有有坚持不懈地决心。所以这应该是Emacs使用者应该持有的态度吧。
光有态度恐怕还不行,还需要方法。那么什么方法呢,其实很简单。只要记住几个非常重要快捷键,一个非常重要的文件,还有若干网站,这就是登山装备了。说的确切点,
快捷键是Ctrl-h t, Ctrl-h i, Ctrl-h f, Ctrl-h v, Ctrl-h d, Ctrl-h b, Ctrl-h n等Ctrl-h作为前缀的帮助快捷键。
文件是大名鼎鼎的dot emacs文件(.emacs配置文件)
网站是http://www.emacswiki.org/,http://emacser.com/about.htm或者直接google好了。
最后的最后,贴出我初养成的神器Emacs的dot emacs。以待后日成为大牛时作个参考。
(require 'ido)
;;使用CVS版本,因为Emacs 23中已经有内欠的CEDET了,所以必须明确load-file (load-file "~/.emacs.d/contrib/cedet/common/cedet.el") (require 'eassist) ;;所有扩展包都在.emacs.d的contrib目录下 (let ((default-directory "~/.emacs.d/")) (normal-top-level-add-subdirs-to-load-path)) (require 'color-theme) (require 'maxframe) (require 'traverselisp) (require 'session) (require 'auto-complete-config) (require 'yasnippet) (require 'ecb-autoloads)
(require 'org) (require 'muse-mode) (require 'muse-publish) (require 'muse-html) (require 'emacs-wiki) (require 'slime) (require 'cc-mode) ;; 还有一些扩展包包含在emacs-goodies-el中,用的是debian系统 (add-to-list 'load-path "/usr/share/emacs/site-lisp/emacs-goodies-el/" t) (require 'tabbar) (require 'highlight-current-line) (require 'pack-windows) ;;;;;;;;;;;;;;;很多适合我的配置;;;;;;;;;;;;;;;;;;;; ;;Custom Setting (custom-set-variables ;; custom-set-variables was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. '(auto-image-file-mode t) '(calendar-mark-diary-entries-flag t) '(calendar-mark-holidays-flag t) '(calendar-week-start-day 1) '(column-number-mode t) '(desktop-base-file-name ".emacs.d/.desktop") '(desktop-base-lock-name ".emacs.d/.desktop.lock") '(desktop-save-mode t) '(display-time-24hr-format t) '(display-time-day-and-date t) '(display-time-format "%A %B %d %T %Z") '(display-time-interval 1) '(display-time-mail-function nil) '(display-time-mode t) '(display-time-world-list (quote (("PST8PDT" "Seattle") ("EST5EDT" "New York") ("GMT0BST" "London") ("CET-1CDT" "Paris") ("IST-5:30" "Bangalore") ("JST-9" "Tokyo") ("CST-8" "BeiJing")))) '(display-time-world-time-format "%A %B %d %T %Z") '(display-time-world-timer-second 1) '(ecb-options-version "2.40") '(ede-project-placeholder-cache-file "~/.emacs.d/.projects.ede") '(gdb-many-windows nil) '(highlight-current-line-globally t nil (highlight-current-line)) '(highlight-current-line-whole-line t) '(holiday-hebrew-holidays nil) '(holiday-islamic-holidays nil) '(holiday-solar-holidays nil) '(ido-create-new-buffer (quote always)) '(ido-enable-last-directory-history nil) '(ido-max-work-directory-list 0) '(ido-max-work-file-list 0) '(ido-mode (quote buffer) nil (ido)) '(ido-record-commands nil) '(image-load-path (quote ("~/.emacs.d/" "/usr/local/share/emacs/23.2/etc/images/" data-directory load-path ""))) '(inhibit-startup-buffer-menu t) '(inhibit-startup-echo-area-message "nixie") '(inhibit-startup-screen t) '(initial-buffer-choice nil) '(initial-scratch-message nil) '(kill-do-not-save-duplicates t) '(kill-ring-max 500) '(legacy-style-world-list (quote (("PST8PDT" "Seattle") ("EST5EDT" "New York") ("GMT0BST" "London") ("CET-1CDT" "Paris") ("IST-5:30" "Bangalore") ("JST-9" "Tokyo") ("CST-8" "BeiJing")))) '(make-backup-files nil) '(max-lisp-eval-depth 1048576) '(max-specpdl-size 1048576) '(save-interprogram-paste-before-kill t) '(scroll-conservatively 100000) '(scroll-margin 3) '(scroll-step 1) '(semantic-c-dependency-system-include-path (quote ("/usr/include" "/usr/local/include/opencv"))) '(semantic-idle-scheduler-idle-time 0.5) '(show-paren-mode t) '(tabbar-background-color "yellow") '(tabbar-mode t nil (tabbar)) '(uniquify-buffer-name-style (quote forward) nil (uniquify)) '(x-select-enable-clipboard t) '(yank-pop-change-selection t) '(yas/trigger-key (kbd "C-x C-y")) '(zoneinfo-style-world-list (quote (("America/Los_Angeles" "Seattle") ("America/New_York" "New York") ("Europe/London" "London") ("Europe/Paris" "Paris") ("Asia/Calcutta" "Bangalore") ("Asia/Tokyo" "Tokyo") ("Asia/BeiJing" "BeiJing"))))) (custom-set-faces ;; custom-set-faces was added by Custom. ;; If you edit it by hand, you could mess it up, so be careful. ;; Your init file should contain only one such instance. ;; If there is more than one, they won't work right. '(highlight-current-line-face ((t (:background "yellow" :foreground "black" :inverse-video t :weight bold)))) '(tabbar-default ((((class color grayscale) (background dark)) (:inherit variable-pitch :background "gray50" :foreground "grey75"))))) (put 'upcase-region 'disabled nil) ;;;;;;;;;;;;;;;;;;;扩展功能的一些初始化;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Color-Theme (eval-after-load "color-theme" '(progn (color-theme-initialize) (color-theme-calm-forest))) ;;Auto-complete (add-to-list 'ac-dictionary-directories "~/.emacs.d/contrib/auto-complete/dict/") (ac-config-default) ;;Yasnippet (yas/initialize) (yas/load-directory "~/.emacs.d/contrib/yasnippet/snippets") ;; Exempted from auto-complete-yasnippet.el (defun ac-yasnippet-candidate-1 (table) (let ((hashtab (yas/snippet-table-hash table)) (parent (yas/snippet-table-parent table)) candidates) (maphash (lambda (key value) (push key candidates)) hashtab) (setq candidates (all-completions ac-prefix (nreverse candidates))) (if parent (setq candidates (append candidates (ac-yasnippet-candidate-1 parent)))) candidates)) (defun ac-yasnippet-candidate () (let ((table (yas/snippet-table major-mode))) (if table (ac-yasnippet-candidate-1 table)))) (defface ac-yasnippet-candidate-face '((t (:background "sandybrown" :foreground "black"))) "Face for yasnippet candidate.") (defface ac-yasnippet-selection-face '((t (:background "coral3" :foreground "white"))) "Face for the yasnippet selected candidate.") (defvar ac-source-yasnippet '((candidates . ac-yasnippet-candidate) (action . yas/expand) (limit . 3) (candidate-face . ac-yasnippet-candidate-face) (selection-face . ac-yasnippet-selection-face)) "Source for Yasnippet.") ;; CEDET (global-ede-mode 1) (semantic-load-enable-minimum-features) (semantic-load-enable-code-helpers) ;;(semantic-load-enable-guady-code-helpers) ;;(semantic-load-enable-excessive-code-helpers) (semantic-load-enable-semantic-debugging-helpers) (global-semantic-highlight-func-mode 1) (if (fboundp #'which-func-mode) (add-hook 'semantic-init-hook (lambda () (which-func-mode 1)))) (global-srecode-minor-mode 1) (when (and (require 'semantic-tag-folding nil 'noerror)) (global-semantic-tag-folding-mode 1) (global-set-key (kbd "C-?") 'global-semantic-tag-folding-mode) (define-key semantic-tag-folding-mode-map (kbd "C-c -") 'semantic-tag-folding-fold-block) (define-key semantic-tag-folding-mode-map (kbd "C-c =") 'semantic-tag-folding-show-block) (define-key semantic-tag-folding-mode-map (kbd "C-c M--") 'semantic-tag-folding-fold-all) (define-key semantic-tag-folding-mode-map (kbd "C-c M-=") 'semantic-tag-folding-show-all)) (enable-visual-studio-bookmarks) ;; Slime (setq slime-lisp-implementations `((sbcl ("~/.emacs.d/contrib/sbcl/src/runtime/sbcl" "--core" ,(file-truename "~/.emacs.d/contrib/sbcl/output/sbcl.core")) :env ,(list (format "SBCL_HOME=%s" (file-truename "~/.emacs.d/contrib/sbcl/contrib/")))))) (slime-setup '(slime-fancy slime-repl slime-scratch slime-editing-commands)) ;;;;;;;;;;;;;;;;;;适合我的设置;;;;;;;;;;;;;;;;;;;;;;;;;;; (global-set-key (kbd "C-x 9") 'maximize-frame);;这个没有ubuntu下面系统默认的alt+f10好用 ;; Enable IDO Mode (ido-mode 1) ;;管理诸多Buffer实在很烦,索性用一个键删除之。开始不习惯,删除一个字母的时候老按delete,结果...... (global-set-key (kbd "<delete>") 'kill-buffer-and-window) ;;把所有不想要的Buffer都一个键删了去。 (defun kill-other-buffers (&rest buffers-not-to-kill) "Kill buffers not listed in arguements. If the arguements are nil, all buffers except current buffer will be killed" (interactive) (let ((buffers-all (buffer-list)) (buffers-not-to-kill (or buffers-not-to-kill (list (current-buffer)))) (kill-buffer-query-functions nil)) (mapc 'kill-buffer (remove-if (lambda (buffer) (memq buffer buffers-not-to-kill)) buffers-all)))) (global-set-key (kbd "S-<delete>") (lambda () (interactive) (kill-other-buffers (current-buffer) (get-buffer "*scratch*") (get-buffer "*Messages*")) (call-interactively 'delete-other-window))) ;; 列出buffer的时候,最好跳到list中,让我选择。 (global-set-key (kbd "C-x C-b") (lambda () (interactive) (call-interactively 'list-buffers) (call-interactively 'other-window))) ;;Save Session when exit (add-hook 'after-init-hook 'session-initialize) ;; Toggle Visual Line Mode (global-set-key (kbd "C-x M-T") (lambda nil (interactive) (if visual-line-mode (visual-line-mode 0)) (setq word-wrap nil) (if truncate-lines (toggle-truncate-lines -1) (toggle-truncate-lines 1)))) (global-set-key (kbd "C-`") (lambda () (interactive) (if (or (not (fboundp 'calendar-exit)) (null (calendar-exit))) (calendar)))) ;; 从emacen.com那里抄来,忒好用了 (defun isearch-current-word-forward (&optional regexp-p no-recursive-edit) (interactive "P/np") (isearch-mode t (not (null regexp-p)) nil (not no-recursive-edit)) (isearch-yank-string (current-word))) (defun isearch-current-word-backward (&optional regexp-p no-recursive-edit) (interactive "P/np") (isearch-mode nil (not (null regexp-p)) nil (not no-recursive-edit)) (isearch-yank-string (current-word))) (global-set-key (kbd "C-*") 'isearch-current-word-forward) (global-set-key (kbd "C-#") 'isearch-current-word-backward) (define-key isearch-mode-map (kbd "C-*") 'isearch-repeat-forward) (define-key isearch-mode-map (kbd "C-#") 'isearch-repeat-backward) ;;;;;;;;;;;;;;;我实在习惯了Visual Studio了,一时还改不过来;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;C/C++ FN keys (defun define-c-mode-fn-key-debug () (define-key c-mode-base-map (kbd "<f5>") 'gud-go) (define-key c-mode-base-map (kbd "<f10>") 'gud-step) (define-key c-mode-base-map (kbd "<f11>") 'gud-next) (define-key c-mode-base-map (kbd "S-<f10>") 'gud-finish) (define-key c-mode-base-map (kbd "<f9>") 'gud-break) (define-key c-mode-base-map (kbd "S-<f9>") 'gud-remove) (define-key gud-mode-map (kbd "<f5>") 'gud-go) (define-key gud-mode-map (kbd "<f10>") 'gud-step) (define-key gud-mode-map (kbd "<f11>") 'gud-next) (define-key gud-mode-map (kbd "S-<f10>") 'gud-finish) (define-key gud-mode-map (kbd "<f9>") 'gud-break)) (defun define-c-mode-fn-key-edit () ;;Code Reference (define-key c-mode-base-map (kbd "<f12>") (lambda () (interactive) (if (semantic-tag-of-class-p (semantic-current-tag) 'include) (progn (push-mark) (when (fboundp 'push-tag-mark) (push-tag-mark)))) (semantic-ia-fast-jump (point)))) (define-key c-mode-base-map (kbd "S-<f12>") (lambda () (interactive) (if (ring-empty-p (oref semantic-mru-bookmark-ring ring)) (error "Semantic Bookmark Ring is empty")) (let* ((ring (oref semantic-mru-bookmark-ring ring)) (alist (semantic-mrub-ring-to-assoc-list ring)) (first (cdr (car alist)))) (if (semantic-equivalent-tag-p (oref first tag) (semantic-current-tag)) (setq first (cdr (car (cdr alist))))) (semantic-mrub-switch-tags first)))) (define-key c-mode-base-map (kbd "M-<f12>") 'eassist-switch-h-cpp) (define-key c-mode-base-map (kbd "M-S-<f12>") 'semantic-analyze-proto-impl-toggle) (define-key c-mode-base-map (kbd "<f8>") (lambda () (interactive) (call-interactively 'ecb-minor-mode))) ;;Compile and Debug (define-key c-mode-base-map (kbd "<f6>") (lambda () (interactive) (compile "make -k"))) (define-key c-mode-base-map (kbd "<f5>") (lambda () (interactive) (call-interactively 'gdb))) (define-key c-mode-base-map (kbd "S-<f5>") (lambda () (interactive) (call-interactively 'gdb) (call-interactively 'gdb-many-windows))) (define-key c-mode-base-map (kbd "M-<f5>") (lambda () (interactive) (compile "make -k") (call-interactively 'gdb))) (define-key c-mode-base-map (kbd "M-S-<f5>") (lambda () (interactive) (compile "make -k") (call-interactively 'gdb) (call-interactively 'gdb-many-windows)))) ;; Close compilation buffer if successful (add-to-list 'compilation-finish-functions (lambda (buffer string) (when (and (string= (buffer-name buffer) "*compilation*") (not (string-match "exited abnormally" string))) (run-at-time 2.5 nil 'delete-windows-on buffer)))) ;; 当gdb/gud结束的时候,最好回到代码窗口,或者回到*scratch*好了 (defun kill-buffers-when-gdb-quit () "Close gdb buffer when gdb quit. This function is invoked when gdb mode is enabled with gdb-mode-hook" (let ((process (get-buffer-process (current-buffer)))) (when (processp process) (set-process-sentinel process (lambda (proc change) (when (string-match "//(finished//|exited//|killed//)" change) (condition-case nil (progn (gdb-many-windows -1) (delete-other-windows (get-buffer-window (switch-to-buffer (condition-case nil (gud-find-file gdb-main-file) (error "*Scratch*"))))) (define-c-mode-fn-key-edit) (gud-reset) (gdb-reset) (kill-buffer (process-buffer proc))) (error nil)))))))) ;; (defun gud-kill () ;; "kill gdb process" ;; (interactive) ;; (with-current-buffer gud-comint-buffer (comint-skip-input)) ;; (set-process-query-on-exit-flag (get-buffer-process gud-comint-buffer) nil) ;; (kill-buffer gud-comint-buffer)) (mapc (lambda (mode-hook) (add-hook mode-hook (lambda () (kill-buffers-when-gdb-quit) (define-c-mode-fn-key-debug)))) (list 'gdb-mode-hook 'gud-mode-hook)) (mapc (lambda (mode-hook) (add-hook mode-hook (lambda () (progn (linum-mode 1) (define-c-mode-fn-key-edit) (tool-bar-add-item "gud" 'gdb 'gdb :visible '(memq major-mode '(c++-mode c-mode))) (tool-bar-add-item "compile" 'compile 'compile :visible '(memq major-mode '(c++-mode c-mode))))))) (list 'c-mode-hook 'c++-mode-hook)) ;; C/C++ Comment Kill (fset 'kill-c-comment (lambda (&optional arg) "Keyboard macro." (interactive "p") (kmacro-exec-ring-item (quote ("/223////|///*/273" 0 "%d")) arg))) (define-key c-mode-base-map (kbd "C-x M-K") 'kill-c-comment) (fset 'kill-c-blank-line (lambda (&optional arg) "Keyboard macro." (interactive "p") (kmacro-exec-ring-item (quote ("/223^[[:space:]]*$" 0 "%d")) arg))) (define-key c-mode-base-map (kbd "C-x C-K") 'kill-c-blank-line)