15

There are times when I'm editing files (Puppet manifests, for example) where I want to align buffer contents on the fly. For example, I know that I can use align-regexp or align to align code like this, as long as I have it selected:

# M-x align-regexp "=>" will line everything up nicely 
file { "/etc/foo":
  ensure => present,
  mode => "0666",
  source => "puppet:///modules/example/foo",
}

But what I really want is to have this happen on the fly -- without having to select a region or the whole buffer. Is there a package or mode that will do this, or is this something that would have to be built in to the mode I'm interested in?

Edit: I asked about Puppet and CFEngine, since that's what I code in most these days and I have a strong preference for aligned text. However, I was also curious (and did not express this well) to know if on-the-fly alignment is a feature that's common in Emacs modes (mainly in modes for programming languages), or if most people align when saving or at some other point.

  • There is expand-region, which is useful for selecting successively bigger scopes. – bastibe Oct 14 '14 at 13:32
  • 1
    Can you describe more on what you'd usually align? Is it just equal signs and in puppet-mode? You can modify the code in this Emacs SE answer for auto-indentation by @Malabarba to get what you want. Simply replace indent-region with align-to-equals and define this function as (defun align-to-equals (begin end) (interactive "r") (align-regexp begin end "\\(\\s-*\\)=" 1 1 nil)); and emacs-lisp-mode-hook with puppet-mode-hook. – Kaushal Modi Oct 14 '14 at 14:16
5

If you'd like to mark a region for auto-alignment, then it's probably not too hard to do with something like:

(require 'cl-lib)

(defvar my--auto-align-overlays nil)
(make-variable-buffer-local 'my--auto-align-overlays)

(defun my-auto-align (beg end regexp)
  (interactive "r\nsRegexp: ")
  (let ((ol (make-overlay beg end)))
    (overlay-put ol 'evaporate t)
    (overlay-put ol 'my--auto-align-regexp regexp)
    (overlay-put ol 'modification-hooks '(my--auto-align-function))
    (push ol my--auto-align-overlays)
    (add-hook 'post-command-hook 'my--auto-align-pch nil t)))

(defun my--auto-align-function (ol &rest _args)
  (cl-pushnew ol my--auto-align-overlays)
  (add-hook 'post-command-hook 'my--auto-align-pch nil t))

(defun my--auto-align-pch ()
  (dolist (ol my--auto-align-overlays)
    (align-regexp (overlay-start ol) (overlay-end ol)
                  (concat "\\(\\s-*\\)" (overlay-get ol 'my--auto-align-regexp))
                  1 align-default-spacing))
  (setq my--auto-align-overlays nil)
  (remove-hook 'post-command-hook 'my--auto-align-pch))

But if you want it to be fully automatic, you'd need to find a way to characterize which regions you want to auto-align (and with which regexp), which is more difficult. I guess you could provide some regexps, and then scan the buffer for consecutive lines that match one of those regexps.

  • I haven't used overlays before, but the Elisp manual seems to say they're for altering appearance -- have I understood that right? If so, does that mean this answer only alters how the buffer looks, rather than the actual contents of the file once it's saved? (It's the latter I was looking for, but changing appearance would be an interesting option.) – Saint Aardvark the Carpeted Oct 15 '14 at 2:54
  • 2
    @SaintAardvarktheCarpeted Overlays are commonly used to alter appearance, but their applicability goes beyond that. In this answer (IIUC), the overlays are used to mark regions which are to be aligned (so the buffer is really changed, not just the appearance). The function my-auto-align is used to define these overlays (mark the regions you want to be aligned). The reason (I think) Stefan is going through all this trouble is that (unlike indentation) if you just try to align the entire buffer, you’ll get undesired results, so you need to align pieces at a time. – Malabarba Oct 15 '14 at 14:26
3

Puppet Mode provides puppet-align-block. You could arrange for this function to be called automatically, e.g. by adding it to post-command-hook locally (untested):

(defun puppet-auto-align ()
  (unless (puppet-in-string-or-comment-p)
    (puppet-align-block)))

(define-minor-mode puppet-auto-align-mode
  :init-value nil
  :keymap nil
  :lighter "PAl"
  :group 'puppet-lint
  (if puppet-auto-align-mode
      (add-hook 'post-command-hook #'puppet-auto-align nil 'local)
    (remove-hook 'post-command-hook #'puppet-auto-align 'local)))

(add-hook 'puppet-mode-hook #'puppet-auto-align-mode)

Note though, that there's a good reason to not do this. It's not particularly clever, and will overwrite any manual alignment you might do anywhere, which will break particularly badly, if the automatic alignment doesn't work for some more convoluted Puppet code.

Hence—speaking as Puppet Mode maintainer—I recommend against it, and we won't help you or accept bug reports, if you are using this code.

Also, for large resource blocks, it might be slow, so you probably want to use an idle timer instead of post-command-hook directly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.