Posted Thursday, November 10th, 2011 at 12:45 pm under me, other, python, tech.

Emacs buffer mode histogram

Tonight I noticed that I had over 200 buffers open in emacs. I’ve been programming a lot in Python recently, so many of them are in Python mode. I wondered how many Python files I had open, and I counted them by hand. About 90. I then wondered how many were in Javascript mode, in RST mode, etc. I wondered what a histogram would look like, for me and for others, at times when I’m programming versus working on documentation, etc.

Because it’s emacs, it wasn’t hard to write a function to display a buffer mode histogram. Here’s mine:

235 buffers open, in 23 distinct modes

91               python +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
47          fundamental +++++++++++++++++++++++++++++++++++++++++++++++
24                  js2 ++++++++++++++++++++++++
21                dired +++++++++++++++++++++
16                 html ++++++++++++++++
 7                 text +++++++
 4                 help ++++
 4           emacs-lisp ++++
 3                   sh +++
 3       makefile-gmake +++
 2          compilation ++
 2                  css ++
 1          Buffer-menu +
 1                 mail +
 1                 grep +
 1      completion-list +
 1                   vm +
 1                  org +
 1               comint +
 1              apropos +
 1                 Info +
 1           vm-summary +
 1      vm-presentation +

Tempting as it is, I’m not going to go on about the heady delights of having a fully programmable editor. You either already know, or you can just drool in slack-jawed wonder.

Unfortunately I’m a terrible emacs lisp programmer. I can barely remember a thing each time I use it. But the interpreter is of course just emacs itself and the elisp documentation is in emacs, so it’s a really fun environment to develop in. And because emacs lisp has a ton of support for doing things to itself, code that acts on emacs and your own editing session or buffers is often very succinct. See for example the save-excursion and with-output-to-temp-buffer functions below.

(defun buffer-mode-histogram ()
  "Display a histogram of emacs buffer modes."
  (let* ((totals ‘())
         (buffers (buffer-list()))
         (total-buffers (length buffers))
         (ht (make-hash-table :testequal)))
      (dolist (buffer buffers)
        (set-buffer buffer)
            ((mode-name (symbol-name major-mode)))
          (puthash mode-name (1+ (gethash mode-name ht 0)) ht))))
    (maphash (lambda (key value)
               (setq totals (cons (list key value) totals)))
    (setq totals (sort totals (lambda (x y) (> (cadr x) (cadr y)))))
    (with-output-to-temp-buffer "Buffer mode histogram"
      (princ (format "%d buffers open, in %d distinct modes\n\n"
                      total-buffers (length totals)))
      (dolist (item totals)
            ((key (car item))
             (count (cadr item)))
          (if (equal (substring key -5) "-mode")
              (setq key (substring key 0 -5)))
          (princ (format "%2d %20s %s\n" count key
                         (make-string count ?+))))))))

Various things about the formatting could be improved. E.g., not use fixed-width fields for the count and the mode names, and make the + signs indicate more than one buffer mode when there are many.

Tags: , ,

  • terrycojones

    Cool :-) Thanks for sharing!

  • Mike

    Cool. I adapted your code to make a histogram of word frequency instead. The regexp is kind of messed up so it matches punctuation though. I also added a bit of code to limit the number of +s to some limit and scale the rest.

    (defun word-histogram-region (posBegin posEnd)
    "Display word histogram showing frequency of word occurrence."
    (interactive "r")
    (message "Counting...")
    (let* ((ht (make-hash-table :test 'equal))
    (totals '()))
    (goto-char posBegin)
    (while (and ( (cadr x) (cadr y)))))
    (with-output-to-temp-buffer "Word histogram"
    (princ (format "%d different wordsnn"
    (length totals)))
    (dolist (item totals)
    ((key (car item))
    (count (cadr item))
    (maxcount (cadr (car totals))))
    (princ (format "%2d %20s %sn" count key
    (make-string (/ (* count (min 36 maxcount)) maxcount) ?+))))))))

  • H Durer

    Quite neat.
    Here is my version derived from yours by incrementally using more loop and less setq:
    (Apologies in advance for having that sneaky side effect of filling the hash-table in the loop which ostensibly calculates only the number of buffers.)

    (defun buffer-mode-histogram ()
    “Display a histogram of emacs buffer modes.”
    (let* ((ht (make-hash-table :test ‘equal))
    (number-of-buffers (loop for buffer being the buffers
    for mode-name = (symbol-name (buffer-local-value ‘major-mode buffer))
    do (incf (gethash mode-name ht 0))
    count 1))
    (totals (sort (loop for key being the hash-keys of ht
    using (hash-values value)
    collect (list key value))
    (lambda (x y) (if (eql (second x) (second y))
    (string-lessp (first x) (first y))
    (> (second x) (second y)))))))
    (with-output-to-temp-buffer “Buffer mode histogram”
    (princ (format “%d buffers open, in %d distinct modesnn”
    number-of-buffers (length totals)))
    (loop for (key count) in totals
    do (princ (format “%2d %20s %sn”
    (if (equal (substring key -5) “-mode”)
    (substring key 0 -5) key)
    (make-string count ?+)))))))

  • terrycojones

    Linked in says we have one friend in common: Gumby :-)

  • terrycojones

    Wow!  That’s fantastic. I didn’t even know pymacs existed :-)   Thanks a lot, I’m going to use it on my next little emacs project….

  • Mark Eichin

    Neat idea, saw it on python planet; reminded me that while I’ve hacked in elisp almost-literally forever, python is my language of choice (and greatest ease), and I’d recently decided to start making more use of pymacs, since I always have it around (at least on Debian and Ubuntu boxes), to overcome the friction I find in elisp… cribbing from some older pymacs code of mine and just looking at your output, it took me less time to come up with a pymacs version than it did to write this comment.  (Not trying to brag :-) just noting that it really is that frictionless compared to elisp.)  To load it, if you have pymacs, just M-: (pymacs-load “mode-stats”) and the m-x mode-stats-mode-stats RET to actually run it.

    Assuming disqus doesn’t mangle this, I’m curious if you find it more readable; the lisp “module” basically gives you everything elisp side, and there’s some complexity regarding data being python-side or emacs-side, plus there can be issues regarding shovelling large objects back and forth.  But for things where elisp is weirdly limiting, I find it a nice approach…

     11 Fundamental     ***********
      2 Python          **
      2 Man             **
      1 Shell           *
      1 Lisp Interaction *
      1 Help            *
      1 Zephyr          *
      1 Completion List *
      1 Dired by name   *
      1 Apropos         *


    # (pymacs-load (expand-file-name “~/elisp/mode-stats”))

    from Pymacs import lisp
    from collections import defaultdict

    def mode_stats():
        stats = defaultdict(int)
        for buf in lisp.buffer_list():
            stats[lisp.buffer_local_value(lisp.intern("mode-name"), buf)] += 1

        outbuf = lisp.get_buffer_create(“*mode-stats*”)
        for bufname in sorted(stats, key=lambda x: stats[x], reverse=True):
            lisp.insert(“%3d %-15s %sn” % (stats[bufname], bufname, “*” * stats[bufname]))

    interactions = {mode_stats: “”}