;;;; command-log.el -- remember what commands the user is using ;;; Time-stamp: <2005-01-18 12:06:37 john> ;;;; This package keeps track of which commands the user has used interactively. ;;; written by John C G Sturdy, October 2004 ;;; IPR declaration: all my original work ;; This program is free software; you can redistribute it and/or modify it ;; under the terms of the GNU General Public License as published by the ;; Free Software Foundation; either version 2 of the License, or (at your ;; option) any later version. ;; This program is distributed in the hope that it will be useful, but ;; WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;; General Public License for more details. ;; You should have received a copy of the GNU General Public License along ;; with this program; if not, write to the Free Software Foundation, Inc., ;; 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ;;; Acknowledgement: Part of the B4STEP project of the SFI (Science ;;; Foundation Ireland) at University of Limerick, written while on a ;;; SOCRATES exchange to Umea University ;;; It is meant for empirical studies in software engineering -- ;;; i.e. looking at how programmers do their thing. Although other ;;; parts of the programmers' environment have been studied by HCI ;;; specialists, editors seem to have received relatively little ;;; attention (something which I am trying to rectify) despite their ;;; being the kind of program which probably receives most of the ;;; users' input. ;;; You could put it in new users' startup files (with their consent), ;;; with command-log-save-to-file in their exit sequence, to find how ;;; fast people increase the number of commands they use as they learn ;;; emacs, and to see how wide the spread of command set sizes is ;;; among a class. ;;; You could also use it to spot which commands deserve to have easy ;;; key-bindings. I might add some code to do analysis of this, ;;; although it's probably too much trouble to record the route by ;;; which each command was reached, and so the analysis would just use ;;; all the keymaps which were around at the time. ;;; With suitable log analysis software, you could spot patterns of ;;; command use that suggest people learning new commands from each ;;; other, for example, as well as looking at the learning curves and ;;; the command set sizes. ;; todo: Entries of commands that have apparently been done 0 times appear in the log, and I don't know why (provide 'command-log) (require 'cl) (defvar command-log (make-vector 1511 nil) "An obarray remembering the commands that have been used.") (defun command-log-pre-command-function () "Note that the current command has been used." (condition-case error-var (let* ((command-symbol (intern (symbol-name this-command) command-log)) (command-count (get command-symbol 'command-count))) (if (integerp command-count) (put command-symbol 'command-count (1+ command-count)) (put command-symbol 'command-count 1))) (error (message "error in command-log-pre-command-function")))) (defun command-log-commands () "Return the list of commands that have been used. Actually this is all commands used since command-log-pre-command-function was put onto pre-command-hook." (let ((result nil)) (mapatoms (lambda (atom) (when atom (let ((count (get atom 'command-count))) (push (cons atom (if (integerp count) count 0)) result)))) command-log) result)) ;;;###autoload (defun command-log-list (&optional alphabetical-order) "List the commands that have been logged." (interactive "P") (let ((commands (sort (command-log-commands) (if alphabetical-order (lambda (a b) (string< (car a) (car b))) (lambda (a b) (if (= (cdr a) (cdr b)) (string< (car a) (car b)) (> (cdr a) (cdr b)))))))) (with-output-to-temp-buffer "*Commands logged*" (mapcar (lambda (c) (princ (format "% 6d: %s\n" (cdr c) (car c)))) commands)) (message "%d different commands used" (length commands)))) ;;;###autoload (defun command-log-save-to-file () "Remember which commands you have used this session." (interactive) (find-file "~/.emacs-command-log") (goto-char (point-max)) (let ((commands (sort (command-log-commands) (lambda (a b) (string< (car a) (car b)))))) (insert (user-real-login-name) "@" (system-name) ": \"" command-log-started-at "-" (current-time-string) "\": " (int-to-string (length commands)) ":" (prin1-to-string commands) "\n")) (basic-save-buffer)) (defvar command-log-groups (mapcar (lambda (g) (cons (car g) (mapcar (lambda (a) (intern (symbol-name a) command-log)) (cdr g)))) '((insert self-insert-command quoted-insert newline) (mistake undo delete-backward-char backward-delete-char-untabify) (search isearch-forward isearch-backward isearch-forward-regexp isearch-backward-regexp) (cursor backward-char forward-char next-line previous-line) (file save-some-buffers find-file insert-file) (history repeat-complex-command previous-history-element next-history-element) (text-copy yank yank-pop kill-region copy-region-as-kill) (smart-insert dabbrev-expand lisp-complete-symbol) (smart-edit delete-blank-lines just-one-space transpose-lines transpose-chars) (structured-movement beginning-of-defun backward-sexp forward-sexp down-list backward-up-list) (structured-edit kill-sexp mark-sexp transpose-sexps) (text-edit backward-kill-word kill-word kill-sentence) (text-movement forward-word backward-word forward-sentence backward-sentence forward-paragraph backward-paragraph) (reformat lisp-indent-line lisp-indent-region lisp-indent-function fill-paragraph fill-individual-paragraphs) )) "How to classify emacs commands. The first entry in each list names a class of commands, and the remaining ones are its members.") (defvar command-log-super-groups '((char insert mistake) (cursor cursor) (smart-move search structured-movement text-movement) (smart-edit smart-edit smart-insert structured-edit text-edit reformat)) "A higher-level grouping of command-log-groups, which see.") (defun command-log-analyze-by-groups (&optional commands) "Look at the commands used (or in the argument if given), and classify them." (unless commands (setq commands (command-log-commands))) (let ((results (mapcar (lambda (l) (cons (car l) (cons 0 (cdr l)))) command-log-groups))) (dolist (command commands) (let ((w results)) (while w (let ((this (car w))) (if (member (car command) (cddr this)) (progn (rplaca (cdr this) (1+ (cadr this))) (setq w nil)) (setq w (cdr w))))))) (mapcar (lambda (r) (cons (car r) (cadr r))) results))) (defun command-log-analyze-by-super-groups (&optional commands) "Like command-log-analyze-by-groups but aggregates the groups." (let ((groups (command-log-analyze-by-groups commands))) )) (defvar command-log-started-at (current-time-string) "When command logging was started in this session. We write this out into the log file, so it can be seen whether two entries are successive saves for the same session, or are non-overlapping separate sessions. (Even I don't always have just one emacs run for the whole day! For example, I quit the emacs to check I was writing the log data successfully...)") (add-hook 'pre-command-hook 'command-log-pre-command-function) ;;; end of command-log.el