Emacs - Timing and Upgrades

I have made a number of upgrades to functions and customizations I’ve previously blogged about, so this post is going to be an aggregation of these improvements. Think of it as a ‘corrections’ post.

In a previous post, I talked about updating imenu-generic-expression for java to work with functions with generic types or annotated arguments. The regular expression there is somewhat inadequate: if you used horrible spacing conventions, it could thing an if statement was a function. This new hook for java-mode to set imenu-generic-expression fixes this case by actually breaking out the annotations, types, and variable names, without just mixing them together as in the previous version.

(add-hook 'java-mode-hook
          '(lambda ()
             (setq imenu-generic-expression
                   `((nil
                      ,(concat
                        "[" c-alpha "_][\]\[." c-alnum "_<> ]+[ \t\n\r]+" ; type spec
                        "\\([" c-alpha "_][" c-alnum "_]+\\)" ; method name
                        "[ \t\n\r]*"
                        ;; An argument list htat is either empty or contains any number
                        ;; of arguments.  An argument is any number of annotations
                        ;; followed by a type spec followed by a word.  A word is an
                        ;; identifier.  A type spec is an identifier, possibly followed
                        ;; by < typespec > possibly followed by [].
                        (concat "("
                                "\\("
                                "[ \t\n\r]*"
                                "\\("
                                "@"
                                "[" c-alpha "_]"
                                "[" c-alnum "._]""*"
                                "[ \t\n\r]+"
                                "\\)*"
                                "\\("
                                "[" c-alpha "_]"
                                "[\]\[" c-alnum "_.]*"
                                "\\("
                                "<"
                                "[ \t\n\r]*"
                                "[\]\[.," c-alnum "_<> \t\n\r]*"
                                ">"
                                "\\)?"
                                "\\(\\[\\]\\)?"
                                "[ \t\n\r]+"
                                "\\)"
                                "[" c-alpha "_]"
                                "[" c-alnum "_]*"
                                "[ \t\n\r,]*"
                                "\\)*"
                                ")"
                                "[ \t\n\r]*"
                                "{"
                                )) 1)))
             ))

The following are a few utilities I wrote in order to write functions such as my-grep. my-fn will take a function with one argument and a prompt, and execute that function with a default of the current word. Defun-my is a macro that defines both the original function and an interactive ‘my’ version of the function. The new my-grep has the same effect as the old my-grep, but uses my-fn.

(defun my-fn (fn prompt)
  "When given a function taking one argument and applying a function to it, will use that function
   and default to the word at point, with a prompt including that word."
  (let ((default (current-word)))
    (let ((needle (read-string (concat prompt " <" default ">: "))))
      (if (equal needle "")
          (funcall fn default)
        (funcall fn needle)))))
 
(defmacro defun-my (name prompt &rest body)
  "Will define both a function and a my- version of the function,
which defaults to the word at point."
  `(progn
     (defun ,name (arg) ,@body)
     (defun ,(intern (concat "my-" (symbol-name name))) ()
       (interactive)
       (my-fn (quote ,name) ,prompt))))
 
(defun my-grep nil
  "Grep the whole directory for something defaults to term at cursor position"
  (interactive)
  (my-fn (lambda (arg) (grep (concat "egrep -s -i -n -r " arg " * " ))) "grep for"))

I also used defun-my in order to upgrade java-describe. The new ‘my-java-descibe-class’ does the same thing, except it defaults to the word at point. I also renamed it to java-describe-class, since I’ve been working on some other Java utilities and wanted to make this name more specific.

(defun-my java-describe-class "Open Javadoc for Class"
  "Loads javadoc for specified class in your browser."
  (interactive "MClass Name: ")
  (let ((my-string (replace-regexp-in-string
                    "^.*class-use/.*\n"
                   ""
                   (shell-command-to-string
                    (concat "find ~/.emacs.d/documentation/java/ -name "
                            arg
                            ".html"))
                   )))
    (string-match "^\\(.*\\)$" my-string)
    (browse-url (match-string 0 my-string))))
 
(global-set-key (kbd "C-x j c") 'my-java-describe-class)

I also upgraded my-help, a function that gives documentation on whatever the point is on. It now checks to see if the buffer is a java buffer; if so, we open the javadoc for the current word instead of calling man on it. I was going to switch the order of checking whether a function or symbol was described first, in the case where a function and variable have the same name, but the issue then is that since function-called-at-point will return the calling function if you are anywhere in a function call, it would frequently mask variable lookup.

(defun my-help ()
  "If function given tries to `describe-function' if variable
uses 'describe-variable', otherwise uses `manual-entry' to display
manpage of a `current-word'."
  (interactive)
  (let ((var (variable-at-point))
        (fn (function-called-at-point)))
    (if (symbolp var)
        (describe-variable var)
      (if fn
          (describe-function fn)
        (if (eq major-mode 'java-mode)
            (java-describe (current-word))
          (man (current-word)))))))

I previously just added visual-line-mode to text-mode-hook, which actually toggled visual-line-mode in the buffer and could lead to strange behaviour. This correction turns visual-line-mode on when entering text modes.

(add-hook 'text-mode-hook '(lambda nil (visual-line-mode 1)))

A reader suggested this improvement in the comments of this post. This will open the current file as sudo at the current position; the old function would lose the position you were at.

(defun sudo-edit-current-file ()
  (interactive)
  (let ((pos (point)))
    (find-alternate-file (concat "/sudo:root@localhost:" (buffer-file-name (current-buffer))))
    (goto-char pos)))

Yank-pop is an advice I describe here to properly indent code fragments. I implemented a suggestion I received in the comments of the linked post in order to make it work better when transient-mark-mode is enabled.

(defadvice yank-pop (after indent-region activate)
  (if (member major-mode
              '(emacs-lisp-mode scheme-mode lisp-mode
                                c-mode c++-mode objc-mode
                                latex-mode plain-tex-mode))
      (let ((mark-even-if-inactive transient-mark-mode))
        (indent-region (region-beginning) (region-end) nil))))

I created a few new functions to keep track of how long each of my customization files take to load. While load time isn’t a problem for me right now, and I could easily fix it if it was by byte-compiling my customizations(right now, the only compiled elisp file I have that is not part of GNU Emacs core is js2.el), I do want to be able to account for what is taking the most time to load. While emacs-init-time tells me how long emacs took to initialize in total, I want a more fine-grained solution. I implemented this by defining time-load-file, a function that would load a file and return a pair consisting of the filename and the time taken to load it in seconds, and changing load-directory to use time-load-file instead of load-file. By saving the results of load-directory, I have a permanent record of the time taken to load each file.

(defun time-load-file (file)
  "Loads a file and returns a pair consisting of the filename and
the time in seconds it took to load."
  (let ((prev-time (float-time)))
    (load-file file)
    (cons file (- (float-time) prev-time))))
 
(defun load-directory (dir)
  "Loads every .el file in a directory."
  (mapcar 'time-load-file (directory-files dir t "\\.el\\'")))
 
(setq customizations-directory-load-times (load-directory "~/.emacs.d/customizations/"))
(setq util-directory-load-times (load-directory "~/.emacs.d/utilities/"))

That’s all I have for now, but I will no doubt improve my customizations further, so once I amass a suitable number I’ll do another one of these posts. If you see anything that should be corrected or could be improved in any of my posts, please leave a comment.

Tags:

2 Responses to “Emacs - Timing and Upgrades”

  1. [...] will be amassed post in the vein of this one. I amassed enough changes to my already blogged-about improvements to emacs to go over what I [...]

  2. [...] utilities to follow need these macros defined. I talked about them previously at: http://nflath.com/2009/08/emacs-timing-and-upgrades/. They are utilities for generating functions that take arguments defaulting to word at point. [...]

Leave a Reply