Emacs is a powerful editor, the most powerful and the fastest programmable editor that I know. Emacs uses a Lisp dialect to create configurations, functions and modes. My favorite mode on Emacs is ECB, a powerful code browsing one, like many IDEs that I know. The difference is that ECB, do not does code indexing until you configure it. For large C projects, I like to use external tools, but not ctags or etags, instead I prefer to use cscope. But you may ask "how can I integrate cscope and Emacs running the ECB mode?". Well, in this post I will try to explain how to integrate both, cscope and Emacs.
To know more about ECB, CEDET and related Emacs modes you can look some articles on the Internet, such as "A Gentle introduction to Cedet", by Alex Ott. All of them would help a lot on configuring both, Emacs and ECB. Also the CEDET — the base package for ECB — and ECB itself are well documented.
The cscope mode that I’m using is the cscope mode contributed to the cscope source tree. It’s called xcscope.el and comes with a small cscope indexing script called cscope-indexer.
On ECB there is a hook that runs on every time that you change your working directory by using the directory navigator. The hook is called ecb-after-directory-change-hook. For my C and C++ projects I use various indentation settings, depending on the project that I’m working on. Then, I’ve created a hook function to ecb-after-directory-change-hook that sets the indenting style depending on the project that I’m working on. I’ve created the dmw-ecb-directory-change-alist variable, and it holds the project path and the indenting style to be applied to the project.
(defvar dmw-ecb-directory-change-alist '(("/work/dmw/c/caffeine" caffeine-c-mode-hook) ("/work/dmw/c/pyxser" python-c-mode-hook) ("/work/dmw/cxx/caffeine++" caffeine-c++-mode-hook) ("/work/dmw/cxx/qstats" caffeine-c++-mode-hook) ("/usr/src/sys" knf) ("/current/usr/src/sys" knf)))
Then, I’ve created the hook to apply the indenting style.
(defun dmw-ecb-directory-change-hook (dirold dirnew) (dolist (prji dmw-ecb-directory-change-alist) (let ((prj-name (first prji)) (prj-match (string-match (concat (first prji) ".*") dirnew))) (if (numberp prj-match) (progn ;; run hooks and functions related to ;; relative path matching on directory browsing (if (>= prj-match 0) (dolist (h (last prji)) (run-hook-with-args h))) ;; run hooks and functions related to ;; full path matching on directory browsing (if (string= prj-name dirnew) (let ((newpath (concat dirnew "/cscope.out")) (dirnew dirnew)) (progn (setq cscope-database-file newpath cscope-do-not-update-database nil cscope-use-relative-path t cscope-initial-directory dirnew) (cscope-index-files dirnew)))) )))))
What does this hook? See the documentation for ecb-after-directory-change-hook. On every directory change on the directory browsing window, the hook runs passing the old directory and the new directory as it’s arguments.
Hook which run directly after the selected directory has changed. This means not only after a click onto a directory in the directory-window of ECB but it means this hook runs always when the current directory changes regardless of the trigger of this change[...]
Then, when the hook matches my C and C++ projects, it pass by the (if (>= prj-match 0) expression and setup the indenting style configured in dmw-ecb-directory-change-alist. If the directory pass by the (if (string= prj-name dirnew) expression, then it runs the block related to the new directory bellow the if sentence. What it does? It sets the cscope-database-file variable to the newpath local variable, which have the value of the new directory concatenated with "/cscope.out", also set some variable to ensure that the cscope database is updated, to enable cscope to use relative paths — this enables that cscope can lookup on different directories if the path changes — and set the initial directory to the new directory. Then, it runs the cscope-indexer. In few words, this hooks refresh the cscope database, and sets a permanent database location relative to the project that I’m working on.
Then, somewhere in your .emacs file, you should put your ECB variables as follows.
(add-hook 'ecb-after-directory-change-hook 'dmw-ecb-directory-change-hook)
Then, just setup the keybinding to use the cscope mode — I do a lot of C programming under emacs, that’s the reason that I’ve configured the keybinding as global, and not local to the C programming mode. Those keybindings bellow are a little bit tricky, you can search for C symbols just by typing Ctrl + Meta + Down. ;)
;; cscope keys (define-key global-map [(control meta s)] 'cscope-set-initial-directory) (define-key global-map [(control meta u)] 'cscope-unset-initial-directory) (define-key global-map [(control meta f)] 'cscope-find-this-symbol) (define-key global-map [(control meta g)] 'cscope-find-global-definition) (define-key global-map [(control meta x)] 'cscope-find-global-definition-no-prompting) (define-key global-map [(control meta m)] 'cscope-pop-mark) (define-key global-map [(control meta n)] 'cscope-next-symbol) (define-key global-map [(control meta N)] 'cscope-next-file) (define-key global-map [(control meta p)] 'cscope-prev-symbol) (define-key global-map [(control meta P)] 'cscope-prev-file) (define-key global-map [(control meta c)] 'cscope-display-buffer) (define-key global-map [(control meta C)] 'cscope-display-buffer-toggle)
I prefer cscope since it have the capability of searching symbols on many ways that other code indexer do not do, and large IDEs like eclipse with CDT gets leaked with large projects like the kernel sources — CDT takes just a few hours to index the complete kernel! — and since cscope is purely made in C and runs really fast.