my-emacs-d/elpa/command-log-mode-20160412.2147/command-log-mode.el
2016-09-27 17:17:26 +02:00

324 lines
12 KiB
EmacsLisp

;;; command-log-mode.el --- log keyboard commands to buffer
;; homepage: https://github.com/lewang/command-log-mode
;; Copyright (C) 2013 Nic Ferrier
;; Copyright (C) 2012 Le Wang
;; Copyright (C) 2004 Free Software Foundation, Inc.
;; Author: Michael Weber <michaelw@foldr.org>
;; Keywords: help
;; Package-Version: 20160412.2147
;; Initial-version: <2004-10-07 11:41:28 michaelw>
;; Time-stamp: <2004-11-06 17:08:11 michaelw>
;; This file 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, or (at your option)
;; any later version.
;; This file 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 GNU Emacs; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;; This add-on can be used to demo Emacs to an audience. When
;; activated, keystrokes get logged into a designated buffer, along
;; with the command bound to them.
;; To enable, use e.g.:
;;
;; (require 'command-log-mode)
;; (add-hook 'LaTeX-mode-hook 'command-log-mode)
;;
;; To see the log buffer, call M-x clm/open-command-log-buffer.
;; The key strokes in the log are decorated with ISO9601 timestamps on
;; the property `:time' so if you want to convert the log for
;; screencasting purposes you could use the time stamp as a key into
;; the video beginning.
;;; Code:
(eval-when-compile (require 'cl))
(defvar clm/log-text t
"A non-nil setting means text will be saved to the command log.")
(defvar clm/log-repeat nil
"A nil setting means repetitions of the same command are merged into the single log line.")
(defvar clm/recent-history-string ""
"This string will hold recently typed text.")
(defun clm/recent-history ()
(setq clm/recent-history-string
(concat clm/recent-history-string
(buffer-substring-no-properties (- (point) 1) (point)))))
(add-hook 'post-self-insert-hook 'clm/recent-history)
(defun clm/zap-recent-history ()
(unless (or (member this-original-command
clm/log-command-exceptions*)
(eq this-original-command #'self-insert-command))
(setq clm/recent-history-string "")))
(add-hook 'post-command-hook 'clm/zap-recent-history)
(defvar clm/time-string "%Y-%m-%dT%H:%M:%S"
"The string sent to `format-time-string' when command time is logged.")
(defvar clm/logging-dir "~/log/"
"Directory in which to store files containing logged commands.")
(defvar clm/log-command-exceptions*
'(nil self-insert-command backward-char forward-char
delete-char delete-backward-char backward-delete-char
backward-delete-char-untabify
universal-argument universal-argument-other-key
universal-argument-minus universal-argument-more
beginning-of-line end-of-line recenter
move-end-of-line move-beginning-of-line
handle-switch-frame
newline previous-line next-line)
"A list commands which should not be logged, despite logging being enabled.
Frequently used non-interesting commands (like cursor movements) should be put here.")
(defvar clm/command-log-buffer nil
"Reference of the currenly used buffer to display logged commands.")
(defvar clm/command-repetitions 0
"Count of how often the last keyboard commands has been repeated.")
(defvar clm/last-keyboard-command nil
"Last logged keyboard command.")
(defvar clm/log-command-indentation 11
"*Indentation of commands in command log buffer.")
(defgroup command-log nil
"Customization for the command log.")
(defcustom command-log-mode-auto-show nil
"Show the command-log window or frame automatically."
:group 'command-log
:type 'boolean)
(defcustom command-log-mode-window-size 40
"The size of the command-log window."
:group 'command-log
:type 'integer)
(defcustom command-log-mode-window-font-size 2
"The font-size of the command-log window."
:group 'command-log
:type 'integer)
(defcustom command-log-mode-key-binding-open-log "C-c o"
"The key binding used to toggle the log window."
:group 'command-log
:type '(radio
(const :tag "No key" nil)
(key-sequence "C-c o"))) ;; this is not right though it works for kbd
(defcustom command-log-mode-open-log-turns-on-mode nil
"Does opening the command log turn on the mode?"
:group 'command-log
:type 'boolean)
(defcustom command-log-mode-is-global nil
"Does turning on command-log-mode happen globally?"
:group 'command-log
:type 'boolean)
;;;###autoload
(define-minor-mode command-log-mode
"Toggle keyboard command logging."
:init-value nil
:lighter " command-log"
:keymap nil
(if command-log-mode
(when (and
command-log-mode-auto-show
(not (get-buffer-window clm/command-log-buffer)))
(clm/open-command-log-buffer))
;; We can close the window though
(clm/close-command-log-buffer)))
(define-global-minor-mode global-command-log-mode command-log-mode
command-log-mode)
(defun clm/buffer-log-command-p (cmd &optional buffer)
"Determines whether keyboard command CMD should be logged.
If non-nil, BUFFER specifies the buffer used to determine whether CMD should be logged.
If BUFFER is nil, the current buffer is assumed."
(let ((val (if buffer
(buffer-local-value command-log-mode buffer)
command-log-mode)))
(and (not (null val))
(null (member cmd clm/log-command-exceptions*)))))
(defmacro clm/save-command-environment (&rest body)
(declare (indent 0))
`(let ((deactivate-mark nil) ; do not deactivate mark in transient
; mark mode
;; do not let random commands scribble over
;; {THIS,LAST}-COMMAND
(this-command this-command)
(last-command last-command))
,@body))
(defun clm/open-command-log-buffer (&optional arg)
"Opens (and creates, if non-existant) a buffer used for logging keyboard commands.
If ARG is Non-nil, the existing command log buffer is cleared."
(interactive "P")
(with-current-buffer
(setq clm/command-log-buffer
(get-buffer-create " *command-log*"))
(text-scale-set 1))
(when arg
(with-current-buffer clm/command-log-buffer
(erase-buffer)))
(let ((new-win (split-window-horizontally
(- 0 command-log-mode-window-size))))
(set-window-buffer new-win clm/command-log-buffer)
(set-window-dedicated-p new-win t)))
(defun clm/close-command-log-buffer ()
"Close the command log window."
(interactive)
(with-current-buffer
(setq clm/command-log-buffer
(get-buffer-create " *command-log*"))
(let ((win (get-buffer-window (current-buffer))))
(when (windowp win)
(delete-window win)))))
;;;###autoload
(defun clm/toggle-command-log-buffer (&optional arg)
"Toggle the command log showing or not."
(interactive "P")
(when (and command-log-mode-open-log-turns-on-mode
(not command-log-mode))
(if command-log-mode-is-global
(global-command-log-mode)
(command-log-mode)))
(with-current-buffer
(setq clm/command-log-buffer
(get-buffer-create " *command-log*"))
(let ((win (get-buffer-window (current-buffer))))
(if (windowp win)
(clm/close-command-log-buffer)
;; Else open the window
(clm/open-command-log-buffer arg)))))
(defun clm/scroll-buffer-window (buffer &optional move-fn)
"Updates `point' of windows containing BUFFER according to MOVE-FN.
If non-nil, MOVE-FN is called on every window which displays BUFFER.
If nil, MOVE-FN defaults to scrolling to the bottom, making the last line visible.
Scrolling up can be accomplished with:
\(clm/scroll-buffer-window buf (lambda () (goto-char (point-min))))
"
(let ((selected (selected-window))
(point-mover (or move-fn
(function (lambda () (goto-char (point-max)))))))
(walk-windows (function (lambda (window)
(when (eq (window-buffer window) buffer)
(select-window window)
(funcall point-mover)
(select-window selected))))
nil t)))
(defmacro clm/with-command-log-buffer (&rest body)
(declare (indent 0))
`(when (and (not (null clm/command-log-buffer))
(buffer-name clm/command-log-buffer))
(with-current-buffer clm/command-log-buffer
,@body)))
(defun clm/log-command (&optional cmd)
"Hook into `pre-command-hook' to intercept command activation."
(clm/save-command-environment
(setq cmd (or cmd this-command))
(when (clm/buffer-log-command-p cmd)
(clm/with-command-log-buffer
(let ((current (current-buffer)))
(goto-char (point-max))
(cond ((and (not clm/log-repeat) (eq cmd clm/last-keyboard-command))
(incf clm/command-repetitions)
(save-match-data
(when (and (> clm/command-repetitions 1)
(search-backward "[" (line-beginning-position -1) t))
(delete-region (point) (line-end-position))))
(backward-char) ; skip over either ?\newline or ?\space before ?\[
(insert " [")
(princ (1+ clm/command-repetitions) current)
(insert " times]"))
(t ;; (message "last cmd: %s cur: %s" last-command cmd)
;; showing accumulated text with interleaved key presses isn't very useful
(when (and clm/log-text (not clm/log-repeat))
(if (eq clm/last-keyboard-command 'self-insert-command)
(insert "[text: " clm/recent-history-string "]\n")))
(setq clm/command-repetitions 0)
(insert
(propertize
(key-description (this-command-keys))
:time (format-time-string clm/time-string (current-time))))
(when (>= (current-column) clm/log-command-indentation)
(newline))
(move-to-column clm/log-command-indentation t)
(princ (if (byte-code-function-p cmd) "<bytecode>" cmd) current)
(newline)
(setq clm/last-keyboard-command cmd)))
(clm/scroll-buffer-window current))))))
(defun clm/command-log-clear ()
"Clear the command log buffer."
(interactive)
(with-current-buffer clm/command-log-buffer
(erase-buffer)))
(defun clm/save-log-line (start end)
"Helper function for `clm/save-command-log' to export text properties."
(save-excursion
(goto-char start)
(let ((time (get-text-property (point) :time)))
(if time
(list (cons start (if time
(concat "[" (get-text-property (point) :time) "] ")
"")))))))
(defun clm/save-command-log ()
"Save commands to today's log.
Clears the command log buffer after saving."
(interactive)
(save-window-excursion
(set-buffer (get-buffer " *command-log*"))
(goto-char (point-min))
(let ((now (format-time-string "%Y-%m-%d"))
(write-region-annotate-functions '(clm/save-log-line)))
(while (and (re-search-forward "^.*" nil t)
(not (eobp)))
(append-to-file (line-beginning-position) (1+ (line-end-position)) (concat clm/logging-dir now))))
(clm/command-log-clear)))
(add-hook 'pre-command-hook 'clm/log-command)
(eval-after-load 'command-log-mode
'(when command-log-mode-key-binding-open-log
(global-set-key
(kbd command-log-mode-key-binding-open-log)
'clm/toggle-command-log-buffer)))
(provide 'command-log-mode)
;;; command-log-mode.el ends here