Posts

  • Editing Links in org-mode

    Links in org-mode by default are displayed as “descriptive” links, meaning they hide their target URLs (or a destination in general). While this looks great, it makes it a bit tricky to figure out how you can edit their URL. There are two easy options:

    1. Just press C-c C-l (org-insert-link) while your point is within a link and you’ll be prompted to edit its URL in the minibuffer. You can use the same command to create new links (when your point is not on an existing link).

    2. You can convert the “descriptive” links to “literal” links1 by invoking the command M-x org-toggle-link-display. You can also toggle between the two display modes for links via the mode’s menu (under “Hyperlinks”).

    And that’s all I have for you today. Admitted, I keep forgetting about this all the time, as I rarely use org-mode, which is the reason I’ve decided to write this short article. Keep hacking!

    1. As a reminder - org-mode links have the format [[URL][DESCRIPTION]]

  • 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!

Subscribe via RSS | View Older Posts