Source code review with Emacs [Part 2]

Series: "Source code review with Emacs"

Now that we can configure packages in an organized way, the first step is to setup the completion framework. This will allow us to quickly search for files, text, etc. Helm integrates nicely with all the needed tools.

You are expected to have followed Source code review with Emacs: Part 1


Helm

Helm is a completion framework that can be used to build completion packages. We don't care about the complexity of the framework, the needed completion packages already exist. We just need to download and configure them.

The following code will setup the needed packages. Add it to the Emacs init file.

;;
;; HELM
;;
(use-package helm
  :ensure t
  :diminish helm-mode
  ;; Helm behavior
  :bind (:map helm-map
              ("<tab>" . 'helm-execute-persistent-action)
              ("C-i"   . 'helm-execute-persistent-action)
              ("C-z"   . 'helm-select-action)
              ("A-v"   . 'helm-previous-page))
  ;; Global shorcuts7
  :bind (("<f2>"    . 'helm-occur)
         ("<f3>"    . 'helm-resume)
         ("M-x"     . 'helm-M-x)
         ("M-y"     . 'helm-show-kill-ring)
         ("M-s o"   . 'helm-occur)
         ("C-'"     . 'helm-semantic-or-imenu)
         ("C-c h"   . 'helm-command-prefix)
         ("C-x b"   . 'helm-mini)
         ("C-x C-f" . 'helm-find-files)
         ("C-c f"   . 'helm-recentf)
         ("C-x C-b" . 'helm-buffers-list))

  :config
  (setq helm-bookmark-show-location t)
  (global-unset-key (kbd "C-x c"))
  ;; fuzzy matching
  (setq helm-mode-fuzzy-match t)
  (setq helm-completion-in-region-fuzzy-match t)
  (setq helm-M-x-fuzzy-match t
        helm-buffers-fuzzy-matching t
        helm-recentf-fuzzy-match t)
  (helm-mode 1))

This code is self explanatory(?). It enables fuzzy search, sets the shortcuts, and enable the mode globally. In the code, C means the Control key, M the Alt key, and s the Super key (windows key) e.g. C-f means Control+f and M-s-o means Alt+Super+o.

The main shortcuts are aimed towards code navigation:

  • When using helm, complete the selection with TAB
  • Select the action to be executed with the selection with C-z
  • Use fuzzy search for file search with C-x C-f
  • Use fuzzy search to find recent visited files with C-c f

Try opening a file with C-x C-f and test the fuzzy search is working by entering "em d". The emacs.d folder should match. "rc ba" should match at least the .bashrc file.

/images/posts/helm_find_file.png


Code navigation

The previous code in tandem with the following code, makes it possible to start navigating code:

;; ggtags can be used to navigate the code and genearte TAG files
;; It supports universal-ctags and pygments backend
(use-package ggtags
  :defer t
  :ensure t)

(use-package helm-gtags
  :defer t
  :ensure t
  :after (helm ggtags)
  :bind (("M-." . 'helm-gtags-dwim)
         ("M-," . 'helm-gtags-pop-stack)))

(use-package helm-ag
  :ensure t
  :defer t
  :after (helm ag)
  :config
  (setq helm-ag-insert-at-point 'symbol))

So far the basic shortcuts to navigate code are:

  • Go to symbol definition by placing the cursor over the symbol and pressing C-. . Go back with M-,
  • List functions defined in the current file with C-'
  • Quickly search for the text under cursor with F2

Although there are smarter ways to find references to symbols, I prefer to use helm-occur to find symbols in the same file. In order to find symbols in other files, I use helm-ag (not properly set yet).

The reason I prefer grepping over smart navigators, is that I usually don't have enough code to make IDEs/navigators work. Furthermore, highly modular code such as code that uses dependency injection cannot be interpreted properly, as the IDE does not have a proper understanding of the dependency injection framework, or there are missing Makefiles. This is of course common with code generated and injected at compile time. Every big enough project will have macros and code generation.

So far we can navigate code by simple grepping, but we cannot navigate using symbols. We will set what is missing in the section below.


C and Java modes

The Java mode is derived from CC mode. Hence, we will configure them at the same time. The following code needs to be added to the init file:

(use-package ggtags
:defer t
:ensure t)

Gtags works with GNU global. The usage is pretty simple. TAG files are created by parsing the source code, and then these TAG files can be used by helm-gtags and ggtags mode to navigate the code. The current configuration prefers helm-gtags for code navigation.

When the M-. command is used for the first time , helm-gtags will try to locate the TAG files. If the files are not found, it will ask for the source code root folder, whether to use gtags to generate the TAG files, and what backend to use to generate the TAG files.

/images/posts/helm_gtags_select_project_root.png /images/posts/helm_gtags_run_gtags.png /images/posts/helm_gtags_select_backend.png

If you want to use Pygments as parsing backend, you can set the environment GTAGSLABEL variable to "Pygments", and use helm-gtags-create-tags to select between backends.

;; Source Code review
;;
;; C mode
;;
(use-package ag)
(use-package cc-mode
  :defer t
  :config
  (setq c-default-style "linux")

  ;; Change font for functions as I want them
  ;; different than other identifiers.
  (make-face 'font-lock-unnecessary-function)
  (set-face-foreground 'font-lock-unnecessary-function "pink")
  (font-lock-add-keywords 'c-mode
			  '(("\\(\\w+\\)\\s-*\("
			     (1 'font-lock-unnecessary-function)))
			  t)

  ;; Open files read-only
  (add-hook 'c-mode-common-hook
	    (lambda ()
	      (when (derived-mode-p 'c-mode 'c++-mode 'java-mode)
		(ggtags-mode t)
                ;; Prefer helm-gtags over ggtags
		(define-key ggtags-mode-map (kbd "M-.") nil)
		(define-key ggtags-mode-map (kbd "M-,") nil)
		(setq buffer-read-only t))))

  ;; Highlight whitespace at the end of a line
  (add-hook 'find-file-hook (lambda () (setq show-trailing-whitespace t))))

;; Comment to use ctags as backend.  Pygments is way better.
(setenv "GTAGSLABEL" "pygments")

This is what the code does:

  • All C, C++, and Java files, are opened in read-only mode. This is because the main use of the setup is for code review.
  • Function calls will be highlighted in pink color. This is a little crude, but needed to facilitate source code review.
  • It removes the ggtags key bindings in favor of helm-ggtags ones.