Posts

  • Auto-create Missing Directories

    Every now and then I’d do something like C-x C-f /path/to/new/dir/some-file.el. As the new folder doesn’t exist Emacs will prompt me to press RET twice to create it. It’s not a big deal, but it always annoys me a little bit and I’m always looking for ways to optimize my workflow.

    At first I thought I’d have to advise find-file and just add some check in the advice whether the destination folder exists. Here’s how this typically works:

    (defun er-auto-create-missing-dirs (orig-fun &rest args)
      (let* ((filename (car args))
             (target-dir (file-name-directory filename)))
        (unless (file-directory-p directory)
          (make-directory directory t))
        (apply orig-fun arg)))
    
    (advice-add 'find-file :around 'er-auto-create-missing-dirs)
    

    That gets the job done, but I’ve never been super fond of using advices as they add some complexity to the process of debugging something. Turns out there’s a much simpler solution, though - the hook find-file-not-found-functions. According to the docs:

    List of functions to be called for find-file on nonexistent file. These functions are called as soon as the error is detected. Variable buffer-file-name is already set up. The functions are called in the order given until one of them returns non-nil.

    This means all we need to do is write a simple hook function:

    (defun er-auto-create-missing-dirs ()
      (let ((target-dir (file-name-directory buffer-file-name)))
        (unless (file-exists-p target-dir)
          (make-directory target-dir t))))
    
    (add-to-list 'find-file-not-found-functions #'er-auto-create-missing-dirs)
    

    Basically we just need to extract the destination folder from the file we’re trying to create and invoke make-directory with its second param instructing it to create all the non-existing folders along the way. The advice example shown earlier is doing exactly the same thing.

    Place one of the above snippets in your Emacs configuration and that’s it. I can never get enough of such small productivity improvements!

    That’s all I have for you today. Keep hacking!

  • Forever Emacs

    Operating systems, GUI toolkits and competing editors come and go, but Emacs is forever!

    – Unknown

    Yesterday it was announced that the once popular Atom editor is going to be discontinued at the end of this year.

    I’ve often said that one of the great advantages of Emacs is that it has stood the test of time1 and will likely be with us for a very long time to come. During the 25 years I’ve been into computers and programming I’ve seen a lot of editors and IDEs rise and fall:

    • Komodo
    • Eclipse
    • NetBeans
    • TextMate
    • LightTable

    And now Atom. Any investment you’ve made to master those tools and build additional plugins for them has been mostly wasted in the end.

    This list also serves to illustrate that just being an open-source project doesn’t mean much, if there’s no real community around the project and the bulk of the development is driven by commercial interests. The moment when such projects lose their backing they typically stagnate and die. It’s not like community project don’t meet their demise from time to time as well, but they are definitely much more resilient than the typical company-driven OSS project.

    Emacs is a true community-driven project and I’m certain that it will outlive many more trendy editors until all is said and done.2 If you’re playing the long game, you better pick the right tools for it.

    In Emacs we trust! M-x forever!

    1. It first appeared way back in 1976! 

    2. Same goes for our evil nemesis vim

  • Detecting Whether Emacs is Running in Terminal or GUI mode

    Occasionally we’d have bits of configuration that are depending on whether Emacs is running in a terminal or in GUI mode. This article aims to cover how to best handle such situations.1

    At first it seems the solution is super simple:

    (if (display-graphic-p)
        ;; GUI mode
        (progn
          (your)
          (code))
        ;; Terminal mode
        (your)
        (code))
    

    In the past the variable window-system provided another way to check for this, but it has been deprecated for a while, so I’d advise against using it.

    While the above solution works, it fails to address one fundamental aspect of Emacs - you can have multiple Emacs frames (or windows, in non-Emacs terminology) associated with one Emacs instance. Some of those might be on a terminal, and others might be on a window system. That is to say, you can get different values of display-graphics-p even within a single Emacs instance.

    For example, you can start a GUI Emacs and then connect to it via emacsclient -t in a terminal; the resulting terminal frame will see a value of nil for display-graphics-p. Similarly, you can start Emacs in daemon mode (emacs --daemon), then later tell it to create a graphical frame with emacsclient -c.

    What this means in practice is that it’s usually best to put code that’s GUI/terminal specific in after-make-frame-functions:

    (add-hook 'after-make-frame-functions
      (lambda ()
        ;; we want some font only in GUI Emacs
        (when (display-graphics-p)
          (set-frame-font "DejaVu Sans Mono 28")))
    

    or

    (add-hook 'after-make-frame-functions
      (lambda ()
        ;; we do something only in terminal Emacs
        (unless (display-graphics-p)
          (xterm-mouse-mode 1)))
    

    That way the check would happen separately for each frame and you’d be able to adjust the settings for it accordingly. And yeah - probably enabling a global mode like xterm-mouse-mode is not the best example for a frame-specific action to take, but I hope you get the general idea.

    Note that the after-make-frame-functions hook isn’t run for the initial frame, so it’s often necessary to also add frame-related hook functions like that above to after-init-hook.

    That’s all I have for you today. Keep hacking!

    1. The article is inspired by this StackOverflow discussion

  • Extract Version Metadata from a Package

    Most Emacs packages need some command to display their version, as that’s useful for debugging purposes (e.g. when submitting a bug report). In the (very) old days each package would have simply some variable with a name like package-version that a command like package-display-version would display somehow (typically in the minibuffer). It doesn’t get simpler than this.

    Then it became popular to have all sorts of package metadata in the comment headers of the Emacs packages. Here’s an example:

    ;; Author: Bozhidar Batsov <bozhidar@nospam.dev>
    ;; URL: https://github.com/bbatsov/projectile
    ;; Keywords: project, convenience
    ;; Version: 2.6.0-snapshot
    ;; Package-Requires: ((emacs "25.1"))
    

    But this opened up the question of how to best extract such metadata… lisp-mnt.el (bundled with Emacs) has some handy functions that can help:

    • lm-author
    • lm-keywords
    • lm-version

    and so on. I hope you get the idea - there’s basically a function for each common metadata property. All of those operate either on the current buffer or an explicitly specified Elisp file. This means that are not very useful when you want to extract metadata from an installed package (usually installed via package.el), as you have to do a bit of extra work to get a handle of the files in the package.

    For a few years I used in all my packages the excellent package pkg-info, but these days both it and its underlying library epl are abandoned, so a while ago I decided I should replace them with something else - ideally, some standard package.el functionality.

    package-get-version was the best solution that I could find. Here’s the description of what the function does:

    Return the version number of the package in which this is used. Assumes it is used from an Elisp file placed inside the top-level directory of an installed ELPA package. The return value is a string (or nil in case we can’t find it).

    As the function was introduced only in Emacs 27 it might not be an option for everyone. That’s how I typically leverage it in most of my projects these days.

    ;;; Version information
    
    (defconst projectile-version "2.6.0-snapshot"
      "The current version of Projectile.")
    
    (defun projectile--pkg-version ()
      "Extract Projectile's package version from its package metadata."
      ;; Use `cond' below to avoid a compiler unused return value warning
      ;; when `package-get-version' returns nil. See #3181.
      ;; FIXME: Inline the logic from package-get-version and adapt it
      (cond ((fboundp 'package-get-version)
             (package-get-version))))
    
    ;;;###autoload
    (defun projectile-version (&optional show-version)
      "Get the Projectile version as string.
    
    If called interactively or if SHOW-VERSION is non-nil, show the
    version in the echo area and the messages buffer."
      (interactive (list t))
      ((let ((version (or (projectile--pkg-version) projectile-version))))
       (if show-version
           (message "Projectile %s" version)
         version)))
    

    As you can see I still use a projectile-version fallback var for the cases where package-get-version fails for one reason or another (e.g. a package wasn’t installed using package.el). You can also see I’m considering inlining package-get-version and adapting it code to make it a bit more flexible. Still, I think the basic solution outlined here is good enough for most people. For everyone else, there’s the bullet-proof approach of magit-version:

    (defun magit-version (&optional print-dest)
      "Return the version of Magit currently in use.
    If optional argument PRINT-DEST is non-nil, output
    stream (interactively, the echo area, or the current buffer with
    a prefix argument), also print the used versions of Magit, Git,
    and Emacs to it."
      (interactive (list (if current-prefix-arg (current-buffer) t)))
      (let ((magit-git-global-arguments nil)
            (toplib (or load-file-name buffer-file-name))
            debug)
        (unless (and toplib
                     (member (file-name-nondirectory toplib)
                             '("magit.el" "magit.el.gz")))
          (let ((load-suffixes (reverse load-suffixes))) ; prefer .el than .elc
            (setq toplib (locate-library "magit"))))
        (setq toplib (and toplib (magit--straight-chase-links toplib)))
        (push toplib debug)
        (when toplib
          (let* ((topdir (file-name-directory toplib))
                 (gitdir (expand-file-name
                          ".git" (file-name-directory
                                  (directory-file-name topdir))))
                 (static (locate-library "magit-version.el" nil (list topdir)))
                 (static (and static (magit--straight-chase-links static))))
            (or (progn
                  (push 'repo debug)
                  (when (and (file-exists-p gitdir)
                             ;; It is a repo, but is it the Magit repo?
                             (file-exists-p
                              (expand-file-name "../lisp/magit.el" gitdir)))
                    (push t debug)
                    ;; Inside the repo the version file should only exist
                    ;; while running make.
                    (when (and static (not noninteractive))
                      (ignore-errors (delete-file static)))
                    (setq magit-version
                          (let ((default-directory topdir))
                            (magit-git-string "describe"
                                              "--tags" "--dirty" "--always")))))
                (progn
                  (push 'static debug)
                  (when (and static (file-exists-p static))
                    (push t debug)
                    (load-file static)
                    magit-version))
                (when (featurep 'package)
                  (push 'elpa debug)
                  (ignore-errors
                    (--when-let (assq 'magit package-alist)
                      (push t debug)
                      (setq magit-version
                            (and (fboundp 'package-desc-version)
                                 (package-version-join
                                  (package-desc-version (cadr it))))))))
                (progn
                  (push 'dirname debug)
                  (let ((dirname (file-name-nondirectory
                                  (directory-file-name topdir))))
                    (when (string-match "\\`magit-\\([0-9].*\\)" dirname)
                      (setq magit-version (match-string 1 dirname)))))
                ;; If all else fails, just report the commit hash. It's
                ;; better than nothing and we cannot do better in the case
                ;; of e.g. a shallow clone.
                (progn
                  (push 'hash debug)
                  ;; Same check as above to see if it's really the Magit repo.
                  (when (and (file-exists-p gitdir)
                             (file-exists-p
                              (expand-file-name "../lisp/magit.el" gitdir)))
                    (setq magit-version
                          (let ((default-directory topdir))
                            (magit-git-string "rev-parse" "HEAD"))))))))
        (if (stringp magit-version)
            (when print-dest
              (princ (format "Magit %s%s, Git %s, Emacs %s, %s"
                             (or magit-version "(unknown)")
                             (or (and (ignore-errors
                                        (magit--version>= magit-version "2008"))
                                      (ignore-errors
                                        (require 'lisp-mnt)
                                        (and (fboundp 'lm-header)
                                             (format
                                              " [>= %s]"
                                              (with-temp-buffer
                                                (insert-file-contents
                                                 (locate-library "magit.el" t))
                                                (lm-header "Package-Version"))))))
                                 "")
                             (magit--safe-git-version)
                             emacs-version
                             system-type)
                     print-dest))
          (setq debug (reverse debug))
          (setq magit-version 'error)
          (when magit-version
            (push magit-version debug))
          (unless (equal (getenv "CI") "true")
            ;; The repository is a sparse clone.
            (message "Cannot determine Magit's version %S" debug)))
        magit-version))
    

    And thought that extracting that version would be easy!

    One more thing before we wrap up - dealing with MELPA package versions. As MELPA overwrites the actual package metadata version with the snapshot version it generates (basically a timestamp), you need to tweak a bit the original code from Projectile I’ve showed earlier. That’s how my CIDER project does the version extraction:

    (defun cider--version ()
      "Retrieve CIDER's version.
    A codename is added to stable versions."
      (if (string-match-p "-snapshot" cider-version)
          (let ((pkg-version (cider--pkg-version)))
            (if pkg-version
                ;; snapshot versions include the MELPA package version
                (format "%s (package: %s)" cider-version pkg-version)
              cider-version))
        ;; stable versions include the codename of the release
        (format "%s (%s)" cider-version cider-codename)))
    

    Basically, if the “real” version has the word “snapshot” in it, we’re appending the package version to the displayed version. This makes it easier to understand what’s the exact snapshot build that someone’s using.

    After writing this article I miss pkg-info even more. Dealing with package metadata should be simpler!

    That’s all I have for you today. Keep hacking!

  • Enable Mouse Support in Terminal Emacs

    I’ve started writing this article 1 year ago and for some reason I never finished. Today I’m changing this!1

    Some text terminals support mouse clicks in the terminal window (shocking, right?). Some Emacs users would like to use the mouse in terminal Emacs. This article is for them.

    TLDR;

    Most people (except GNU/Linux users) should add this to their init.el:

    (xterm-mouse-mode 1)
    

    GNU/Linux users should add this to their init.el:

    (gpm-mouse-mode 1)
    

    You might want to wrap this config in some check that you’re actually running terminal Emacs (e.g. emacs -nw):

    (unless (display-graphic-p)
      (xterm-mouse-mode 1))
    

    Now let’s expand on this a bit.

    In a terminal emulator which is compatible with xterm, you can use M-x xterm-mouse-mode to give Emacs control over simple uses of the mouse - basically, only non-modified single clicks are supported. Newer versions of xterm also support mouse-tracking. The normal xterm mouse functionality for such clicks is still available by holding down the Shift key when you press the mouse button. xterm-mouse-mode is a global minor mode.

    In the console on GNU/Linux, you can use M-x gpm-mouse-mode to enable mouse support. You must have the gpm server installed and running on your system in order for this to work. Note that when this mode is enabled, you cannot use the mouse to transfer text between Emacs and other programs which use GPM. This is due to limitations in GPM and the Linux kernel.

    Obviously xterm-mouse-mode will work on GNU/Linux system, but you’ll get better results with gpm-mouse-mode.

    1. Probably because I almost never use the mouse in Emacs. Or because I’m very lazy. Or because I have this tendency to start more things than I can finish. 

Subscribe via RSS | View Older Posts