├── testrun.sh ├── testrun └── testinit.el ├── Cask ├── features ├── support │ └── env.el ├── step-definitions │ └── dot-mode-steps.el └── dot-mode.feature ├── README.org └── dot-mode.el /testrun.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | cask emacs -Q --load testrun/testinit.el --load features/support/env.el 3 | -------------------------------------------------------------------------------- /testrun/testinit.el: -------------------------------------------------------------------------------- 1 | (require 'ecukes) 2 | (require 'espuds) 3 | 4 | (require 'smex) 5 | (global-set-key (kbd "M-x") 'smex) 6 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source melpa) 3 | 4 | (package "dot-mode" "1.12" "Dot Mode") 5 | 6 | (development 7 | (depends-on "smex") 8 | (depends-on "undo-tree") 9 | (depends-on "ecukes") 10 | (depends-on "espuds")) 11 | -------------------------------------------------------------------------------- /features/support/env.el: -------------------------------------------------------------------------------- 1 | (require 'f) 2 | 3 | (defvar dot-mode-support-path 4 | (f-dirname load-file-name)) 5 | 6 | (defvar dot-mode-features-path 7 | (f-parent dot-mode-support-path)) 8 | 9 | (defvar dot-mode-root-path 10 | (f-parent dot-mode-features-path)) 11 | 12 | (add-to-list 'load-path dot-mode-root-path) 13 | 14 | (require 'dot-mode) 15 | (require 'espuds) 16 | (require 'ert) 17 | 18 | (Setup 19 | ;; Before anything has run 20 | ) 21 | 22 | (Before 23 | ;; Before each scenario is run 24 | ) 25 | 26 | (After 27 | ;; After each scenario is run 28 | ) 29 | 30 | (Teardown 31 | ;; After when everything has been run 32 | ) 33 | -------------------------------------------------------------------------------- /features/step-definitions/dot-mode-steps.el: -------------------------------------------------------------------------------- 1 | ;; This file contains your project specific step definitions. All 2 | ;; files in this directory whose names end with "-steps.el" will be 3 | ;; loaded automatically by Ecukes. 4 | 5 | (Given "^I load \"\\([^\"]+\\)\"$" 6 | (lambda (library) 7 | (require (intern library)))) 8 | 9 | (And "^I activate undo-tree$" 10 | (lambda () 11 | (undo-tree-mode 1))) 12 | 13 | (And "^I bind M-x to smex$" 14 | (lambda () 15 | (global-set-key (kbd "M-x") 'smex))) 16 | 17 | ;; NOTE Don't have a generalised version for see vs only see because espuds 18 | ;; already has a (Then "^I should see$") step 19 | (Then "^I should only see\\(?: \"\\([^\"]*\\)\"\\|:\\)$" 20 | "Asserts that the current buffer just has some text." 21 | (lambda (expected) 22 | (let ((actual (buffer-string)) 23 | (message "Expected\n%s\nto match:\n%s")) 24 | (cl-assert (string= expected actual) nil message expected actual)))) 25 | 26 | (Given "^I bind \"\\([^\"]+\\)\" to \"\\([^\"]+\\)\"$" 27 | (lambda (command key) 28 | (global-set-key (kbd key) (intern command)))) 29 | 30 | (Given "^I empty the command buffer$" 31 | (lambda () (setq dot-mode-cmd-buffer nil))) 32 | 33 | 34 | (Then "^The last message should be \"\\([^\"]+\\)\"$" 35 | (lambda (message) 36 | (let ((msg "Expected '%s' to be the last printed message, but it was not.") 37 | (last-msg (car (last (-map 's-trim ecukes-message-log))))) 38 | (setq message (s-replace "\\\"" "\"" message)) 39 | (cl-assert (s-equals? last-msg message) nil msg message)))) 40 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * dot-mode.el 2 | 3 | dot-mode.el is a minor mode for GNU Emacs that emulates the '.' command in vi. 4 | 5 | See: http://wyrick.org/source/elisp/dot-mode/ 6 | 7 | ** Aim 8 | This mode is written to address one argument in the emacs vs. vi jihad :-) It 9 | emulates the vi `redo' command, repeating the immediately preceding sequence of 10 | commands. 11 | This is done by recording input commands which change the buffer, i.e. not 12 | motion commands. 13 | ** Use 14 | *** Installation 15 | Add to =load-path=, and =(require 'dot-mode)=. 16 | Run the command =dot-mode= to start =dot-mode=, if you want =dot-mode= to be 17 | enabled in all buffers you can have the sexp =(global-dot-mode t)= in your 18 | config. 19 | *** Customization 20 | There are three variables that allow you to modify how dot-mode 21 | behaves: 22 | #+BEGIN_SRC elisp 23 | dot-mode-verbose 24 | dot-mode-ignore-undo 25 | dot-mode-global-mode 26 | #+END_SRC 27 | 28 | =dot-mode-ignore-undo= defaults to t. 29 | When nil, it will record keystrokes that generate an undo just like any other 30 | keystroke that changed the buffer. I personally find that annoying, but if you 31 | want dot-mode to always remember your undo's: 32 | : (setq dot-mode-ignore-undo nil) 33 | Besides, you can always use dot-mode-override to record an undo when you need to 34 | (or even M-x undo). 35 | 36 | =dot-mode-global-mode= - defaults to t. 37 | When t, dot-mode only has one keyboard command buffer. 38 | That means you can make a change in one buffer, switch buffers, then repeat the 39 | change. 40 | When set to nil, each buffer gets its own command buffer. 41 | That means that after making a change in a buffer, if you switch buffers, that 42 | change cannot repeated. 43 | If you switch back to the first buffer, your change can then be repeated again. 44 | This has a nasty side effect if your change yanks from the =kill-ring= (You 45 | could end up yanking text you killed in a different buffer). 46 | If you want to set this to nil, you should do so before dot-mode is activated on 47 | any buffers. 48 | Otherwise, you may end up with some buffers having a local command buffer and 49 | others using the global one. 50 | *** Usage instructions 51 | 52 | `C-.' is bound to =dot-mode-execute=, which executes the buffer of 53 | stored commands as a keyboard macro. 54 | 55 | `C-M-.' is bound to =dot-mode-override=, which will cause =dot-mode= 56 | to remember the next keystroke regardless of whether it 57 | changes the buffer and regardless of the value of the 58 | =dot-mode-ignore-undo= variable. 59 | 60 | `C-c-.' is bound to =dot-mode-copy-to-last-kbd-macro=, which will 61 | copy the current dot mode keyboard macro to the =last-kbd-macro= 62 | variable. It can then be executed via =call-last-kbd-macro= 63 | (normally bound to `C-x-e'), named via =name-last-kbd-macro=, 64 | and then inserted into your .emacs via =insert-kbd-macro=. 65 | ** For Contributors 66 | Installation of the development environment can be done with =cask install=. 67 | 68 | Tests are written using [[https://github.com/ecukes/ecukes][ecukes]], and can be run with =cask exec ecukes=. 69 | 70 | The script =./testrun.sh= starts an emacs instance in the environment that the 71 | tests are run under. This instance begins with =dot-mode= loaded, but not turned 72 | on. 73 | -------------------------------------------------------------------------------- /features/dot-mode.feature: -------------------------------------------------------------------------------- 1 | Feature: Repeats changes to buffer 2 | In order to cut down on typing 3 | As a user 4 | I want to be able to repeat my last change 5 | 6 | Scenario: Insert text twice 7 | Given I clear the buffer 8 | And I turn on dot-mode 9 | Given I type "Hello there this is a test" 10 | And I press "C-b" 11 | And I press "C-f" 12 | And I press "C-." 13 | Then I should see: 14 | """ 15 | Hello there this is a testHello there this is a test 16 | """ 17 | 18 | Scenario: Use commands twice 19 | Given I go to point "10" 20 | And I press "C-k" 21 | And I press "C-4 C-b" 22 | And I press "C-." 23 | Then I should only see: 24 | """ 25 | Hello 26 | """ 27 | 28 | Feature: Captures extended commands 29 | 30 | Scenario: Captures extended commands 31 | Given I start an action chain 32 | And I press "M-x" 33 | And I type "backward-delete-char" 34 | And I execute the action chain 35 | Then I should see "Hell" 36 | Given I press "C-." 37 | Then I should only see "Hel" 38 | 39 | Scenario: Captures extended commands that prompt for input 40 | Given I start an action chain 41 | And I press "M-x" 42 | And I type "insert-char" 43 | And I press "" 44 | And I press "69" 45 | And I execute the action chain 46 | Then I should only see "Heli" 47 | Given I press "C-." 48 | Then I should only see "Helii" 49 | 50 | Scenario: Works no matter what key execute-extended-command is bound to 51 | Given I bind "execute-extended-command" to "C-'" 52 | And I start an action chain 53 | And I press "C-'" 54 | And I type "insert-char" 55 | And I press "" 56 | And I press "69" 57 | And I execute the action chain 58 | Then I should only see "Heliii" 59 | Given I press "C-." 60 | Then I should only see "Heliiii" 61 | 62 | 63 | Feature: Ignores undo 64 | To keep things understandable 65 | Dot mode should ignore undo commands 66 | 67 | Scenario: Ignores the undo command 68 | Given I clear the buffer 69 | # Press a command to break apart the current command 70 | And I press "C-l" 71 | And I type "Goodbye" 72 | Then I should only see "Goodbye" 73 | Given I press "C-/" 74 | Then the buffer should be empty 75 | Given I press "C-." 76 | Then I should only see "Goodbye" 77 | 78 | Scenario: Ignores undo-tree-undo and undo-tree-redo 79 | Given I load "undo-tree" 80 | And I activate undo-tree 81 | And I clear the buffer 82 | And I press "C-l" 83 | And I type "Goodbye" 84 | Given I press "C-/" 85 | Then the buffer should be empty 86 | Given I press "C-." 87 | Then I should only see "Goodbye" 88 | 89 | Feature: Integrates with smex 90 | In order to use both dot-mode and smex 91 | I want to not have to worry about it 92 | 93 | Scenario: Captures smex extended commands 94 | Given I load "smex" 95 | And I bind M-x to smex 96 | And I clear the buffer 97 | And I insert "aaa" 98 | Then I should only see "aaa" 99 | Given I start an action chain 100 | And I press "M-x" 101 | And I type "backward-delete-char" 102 | And I execute the action chain 103 | Then I should only see "aa" 104 | Given I press "C-." 105 | Then I should only see "a" 106 | 107 | Scenario: Captures smex extended commands with minibuffer text 108 | Given I start an action chain 109 | And I press "M-x" 110 | And I type "insert-char" 111 | And I press "" 112 | And I press "69" 113 | And I execute the action chain 114 | Then I should only see "ai" 115 | Given I press "C-." 116 | Then I should only see "aii" 117 | 118 | Feature: Can override motion commands 119 | In order to include motion commands 120 | As a dot-mode user 121 | I use dot-mode-override 122 | 123 | Scenario: Override in the middle 124 | Given I clear the buffer 125 | And I press "C-l" 126 | And I start an action chain 127 | And I type "a" 128 | And I press "C-M-." 129 | And I press "C-b" 130 | And I type "b" 131 | And I execute the action chain 132 | Then I should only see "ba" 133 | And the cursor should be at point "2" 134 | Given I press "C-." 135 | Then the cursor should be at point "3" 136 | And I should only see "bbaa" 137 | 138 | Scenario: Override at beginning of command 139 | Given I clear the buffer 140 | And I insert: 141 | """ 142 | First line 143 | Second line 144 | Third line 145 | """ 146 | And I go to beginning of buffer 147 | And I start an action chain 148 | And I press "C-M-." 149 | And I press "C-n" 150 | And I press "C-M-." 151 | And I press "C-e" 152 | And I type " modified" 153 | And I execute the action chain 154 | Then I should only see: 155 | """ 156 | First line 157 | Second line modified 158 | Third line 159 | """ 160 | Given I press "C-." 161 | Then I should only see: 162 | """ 163 | First line 164 | Second line modified 165 | Third line modified 166 | """ 167 | 168 | 169 | Feature: global-dot-mode does not recurse on error 170 | If an error occurs 171 | Dot mode does not recurse after attempting to repeat 172 | 173 | Scenario: Repeat an empty buffer 174 | Given I empty the command buffer 175 | And I press "C-." 176 | Then The last message should be "Nothing to repeat" 177 | 178 | Scenario: Repeat an empty buffer in global-dot-mode 179 | Given I turn on global-dot-mode 180 | And I press "C-." 181 | Then The last message should be "Nothing to repeat" 182 | And I press "C-." 183 | Then The last message should be "Nothing to repeat" 184 | And I press "C-." 185 | Then The last message should be "Nothing to repeat" 186 | 187 | Feature: Accounts for universal-argument and digit-argument 188 | If I modify a command with arguments 189 | Dot mod should record that 190 | 191 | Scenario Outline: I start a modification with a digit-argument 192 | Given I clear the buffer 193 | And I press "C-l" 194 | And I start an action chain 195 | And I press "" 196 | And I type "a" 197 | And I execute the action chain 198 | Then I should only see "aaaa" 199 | Given I press "C-." 200 | Then I should only see "aaaaaaaa" 201 | 202 | Examples: 203 | | four-argument | 204 | | C-u | 205 | | C-u 4 | 206 | | C-4 | 207 | 208 | Scenario Outline: I start a modification with negative-argument 209 | Given I clear the buffer 210 | And I insert "aaaa" 211 | Given I type "l" 212 | And I press "C-b" 213 | And I start an action chain 214 | And I press "" 215 | And I press "C-d" 216 | And I execute the action chain 217 | Then I should only see "aaal" 218 | Given I press "C-." 219 | Then I should only see "aal" 220 | 221 | Examples: 222 | | negative-argument | 223 | | C-- | 224 | | C-u - | 225 | | C-- 1 | 226 | | C-u - 1 | 227 | 228 | Scenario Outline: I use a digit-argument in the middle of a modification 229 | Given I clear the buffer 230 | And I press "C-l" 231 | And I start an action chain 232 | And I type "he" 233 | And I press "" 234 | And I type "lo" 235 | And I execute the action chain 236 | Then I should see "hellllo" 237 | Given I press "C-." 238 | Then I should only see "hellllohellllo" 239 | 240 | Examples: 241 | | four-argument | 242 | | C-u | 243 | | C-u 4 | 244 | | C-4 | 245 | 246 | 247 | Scenario Outline: I use negative-argument in the middle of a modification 248 | Given I clear the buffer 249 | And I insert "abababab" 250 | And I go to point "5" 251 | And I start an action chain 252 | And I press "C-d" 253 | And I press "" 254 | And I press "C-d" 255 | And I execute the action chain 256 | Then I should only see "ababab" 257 | Given I press "C-." 258 | Then I should only see "abab" 259 | 260 | 261 | Examples: 262 | | negative-argument | 263 | | C-- | 264 | | C-u - | 265 | | C-- 1 | 266 | | C-u - 1 | 267 | 268 | 269 | # Override should always apply to the actual command, not to the 270 | # digit-argument. 271 | # i.e. override digit-argument command == digit-argument override command 272 | # also, as example 273 | # override C-u 34 command == 274 | # C-u override 34 command == 275 | # C-u 3 override 4 command == 276 | # C-u 34 override command 277 | 278 | Scenario Outline: I use override with a digit argument 279 | Given I clear the buffer 280 | And I insert "ababababab" 281 | And I start an action chain 282 | And I press "" 283 | And I press "C-k" 284 | And I press "" 285 | And I press "C-k" 286 | And I execute the action chain 287 | Then I should only see "ababab" 288 | Given I press "C-." 289 | Then I should only see "ab" 290 | 291 | Examples: 292 | | override-backwards-2 | 293 | | C-M-. C-2 C-b | 294 | | C-M-. C-u 2 C-b | 295 | | C-M-. C-u - 2 C-f | 296 | | C-2 C-M-. C-b | 297 | | C-u C-M-. 2 C-b | 298 | | C-u C-M-. - 2 C-f | 299 | | C-u 2 C-M-. C-b | 300 | | C-u - C-M-. 2 C-f | 301 | | C-u - 2 C-M-. C-f | 302 | -------------------------------------------------------------------------------- /dot-mode.el: -------------------------------------------------------------------------------- 1 | ;;; dot-mode.el --- minor mode to repeat typing or commands 2 | 3 | ;;; Copyright (C) 1995 James Gillespie 4 | ;;; Copyright (C) 2000 Robert Wyrick (rob@wyrick.org) 5 | 6 | ;; Author: Robert Wyrick 7 | ;; Maintainer: Robert Wyrick 8 | ;; Keywords: convenience 9 | ;; Version: 1.13 10 | ;; URL: https://github.com/wyrickre/dot-mode 11 | ;; Package-Requires: ((emacs "24.3")) 12 | 13 | ;;; This program is free software; you can redistribute it and/or modify 14 | ;;; it under the terms of the GNU General Public License as published by 15 | ;;; the Free Software Foundation; either version 1, or (at your option) 16 | ;;; any later version. 17 | 18 | ;;; This program is distributed in the hope that it will be useful, 19 | ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | ;;; GNU General Public License for more details. 22 | 23 | ;;; A copy of the GNU General Public License can be obtained from 24 | ;;; the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 25 | ;;; 02139, USA. 26 | 27 | ;;; Commentary: 28 | 29 | ;; 30 | ;; Purpose of this package: minor mode to repeat typing or commands 31 | ;; 32 | ;; Installation instructions 33 | ;; 34 | ;; Install this file somewhere in your load path, byte-compile it and 35 | ;; add one of the following to your .emacs file (remove the comment 36 | ;; delimiters ;-) 37 | ;; 38 | ;; If you only want dot-mode to activate when you press "C-.", add the 39 | ;; the following to your .emacs: 40 | ;; 41 | ;; (autoload 'dot-mode "dot-mode" nil t) ; vi `.' command emulation 42 | ;; (global-set-key [(control ?.)] (lambda () (interactive) (dot-mode 1) 43 | ;; (message "Dot mode activated."))) 44 | ;; 45 | ;; If you want dot-mode all the time (like me), add the following to 46 | ;; your .emacs: 47 | ;; 48 | ;; (require 'dot-mode) 49 | ;; (add-hook 'find-file-hooks 'dot-mode-on) 50 | ;; 51 | ;; You may still want to use the global-set-key above.. especially if you 52 | ;; use the *scratch* buffer. 53 | ;; 54 | ;; To toggle dot mode on or off type `M-x dot-mode' 55 | ;; 56 | ;; There are only two variables that allow you to modify how dot-mode 57 | ;; behaves: 58 | ;; dot-mode-ignore-undo 59 | ;; dot-mode-global-mode 60 | ;; 61 | ;; dot-mode-ignore-undo - defaults to t. When nil, it will record keystrokes 62 | ;; that generate an undo just like any other keystroke that changed the 63 | ;; buffer. I personally find that annoying, but if you want dot-mode to 64 | ;; always remember your undo's: 65 | ;; (setq dot-mode-ignore-undo nil) 66 | ;; Besides, you can always use dot-mode-override to record an undo when 67 | ;; you need to (or even M-x undo). 68 | ;; 69 | ;; dot-mode-global-mode - defaults to t. When t, dot-mode only has one 70 | ;; keyboard command buffer. That means you can make a change in one 71 | ;; buffer, switch buffers, then repeat the change. When set to nil, 72 | ;; each buffer gets its own command buffer. That means that after 73 | ;; making a change in a buffer, if you switch buffers, that change 74 | ;; cannot repeated. If you switch back to the first buffer, your 75 | ;; change can then be repeated again. This has a nasty side effect 76 | ;; if your change yanks from the kill-ring (You could end up 77 | ;; yanking text you killed in a different buffer). 78 | ;; If you want to set this to nil, you should do so before dot-mode 79 | ;; is activated on any buffers. Otherwise, you may end up with some 80 | ;; buffers having a local command buffer and others using the global 81 | ;; one. 82 | ;; 83 | ;; Usage instructions: 84 | ;; 85 | ;; `C-.' is bound to dot-mode-execute, which executes the buffer of 86 | ;; stored commands as a keyboard macro. 87 | ;; 88 | ;; `C-M-.' is bound to dot-mode-override, which will cause dot-mode 89 | ;; to remember the next keystroke regardless of whether it 90 | ;; changes the buffer and regardless of the value of the 91 | ;; dot-mode-ignore-undo variable. 92 | ;; 93 | ;; `C-c-.' is bound to dot-mode-copy-to-last-kbd-macro, which will 94 | ;; copy the current dot mode keyboard macro to the last-kbd-macro 95 | ;; variable. It can then be executed via call-last-kbd-macro 96 | ;; (normally bound to `C-x-e'), named via name-last-kbd-macro, 97 | ;; and then inserted into your .emacs via insert-kbd-macro. 98 | ;; 99 | ;; Known bugs: 100 | ;; 101 | ;; none 102 | ;; 103 | 104 | ;;; COMMENTARY 105 | ;;; 106 | ;;; This mode is written to address one argument in the emacs vs. vi 107 | ;;; jihad :-) It emulates the vi `redo' command, repeating the 108 | ;;; immediately preceding sequence of commands. This is done by 109 | ;;; recording input commands which change the buffer, i.e. not motion 110 | ;;; commands. 111 | 112 | ;;; DESIGN 113 | ;;; 114 | ;;; The heart of this minor mode is a state machine. The function 115 | ;;; dot-mode-after-change is called from after-change-functions and 116 | ;;; sets a variable (is there one already? I couldn't find it) which 117 | ;;; is examined by dot-mode-loop, called from from post-command-hook. 118 | ;;; This variable, dot-mode-changed, is used in conjunction with 119 | ;;; dot-mode-state to move to the next state in the state machine. 120 | ;;; The state machine is hard coded into dot-mode-loop in the 121 | ;;; interests of speed; it uses two normal states (idle and store) 122 | ;;; and two corresponding override states which allow the user to 123 | ;;; forcibly store commands which do not change the buffer. 124 | ;;; 125 | ;;; TODO 126 | ;;; * Explore using recent-keys for this functionality 127 | 128 | ;;; Code: 129 | 130 | (defconst dot-mode-version "1.13" 131 | "Report bugs to: Robert Wyrick ") 132 | 133 | ;;; CHANGE HISTORY 134 | ;;; 135 | ;;; 1.1 136 | ;;; Wrote dot-mode.el 137 | ;;; 138 | ;;; 1.2 139 | ;;; At the suggestion of Scott Evans , added 140 | ;;; 'dot-mode-override' to allow the user to force dot mode to store a 141 | ;;; motion command 142 | ;;; 143 | ;;; 1.3 144 | ;;; Changed dot-mode-loop to use a state machine instead of several 145 | ;;; booleans 146 | ;;; 147 | ;;; 1.4 148 | ;;; Hard coded the state machine into dot-mode-loop in the hope of 149 | ;;; speeding it up 150 | ;;; 151 | ;;; 1.5 152 | ;;; Ported to GNU Emacs - nearly: the keymap doesn't seem to install 153 | ;;; correctly. 154 | ;;; 155 | ;;; 1.6 156 | ;;; Rob Wyrick (that's me) took over maintenance of the package from 157 | ;;; Jim Gillespie. 158 | ;;; 159 | ;;; In some versions of Emacs, (this-command-keys) returns a empty 160 | ;;; vector by the time it is called from the 'post-command-hook. 161 | ;;; So, I split the functionality... now dot-mode-command-keys 162 | ;;; stores (this-command-keys) output in a temp variable to be used 163 | ;;; by dot-mode-loop and dot-mode-command-keys is called from the 164 | ;;; pre-command-hook. Also re/ported to XEmacs/GNU Emacs. It works 165 | ;;; on both now. dot-mode-command-keys could have been put on the 166 | ;;; after-change-functions hook, but I've begun preliminary work to 167 | ;;; capture what's going on in the minibuffer and I'm certain I need 168 | ;;; it where it is. 169 | ;;; 170 | ;;; 1.7 171 | ;;; Added my first attempt to capture what the user is doing with 172 | ;;; execute-extended-command (M-x). It even works if the executed 173 | ;;; command prompts the user. 174 | ;;; Also added some error recovery if the user interrupts or there is 175 | ;;; an error during execution of the stored macro. 176 | ;;; 177 | ;;; 1.8 178 | ;;; Second attempt to capture what the user is doing with 179 | ;;; execute-extended-command (M-x). The previous version didn't work 180 | ;;; in XEmacs. This version works in both XEmacs and GNUEmacs. 181 | ;;; 182 | ;;; 1.9 183 | ;;; Third attempt to capture what the user is doing with 184 | ;;; execute-extended-command (M-x). Wow was I making things hard. 185 | ;;; It's cost me a lot of version numbers in a short amount of time, 186 | ;;; so we won't discuss my previous attempts. *grin* My second attempt 187 | ;;; worked just fine, but it was more complicated and maybe not as 188 | ;;; portable to older version of X/GNU Emacs. 189 | ;;; Other things: 190 | ;;; - Yet another restructuring of the code. By doing so, 191 | ;;; quoted-insert (C-q) is properly stored by dot-mode. 192 | ;;; (quoted-insert has been broken since ver 1.6) 193 | ;;; - Deleted an extraneous state and the "extended-state" added 194 | ;;; in ver 1.8. We're down to just two normal states and two 195 | ;;; override states. 196 | ;;; - Added dot-mode-ignore-undo and dot-mode-global-mode variables 197 | ;;; as well as the new function dot-mode-copy-to-last-kbd-macro. 198 | ;;; 199 | ;;; 1.10 200 | ;;; Fixed a bug where the META key wasn't properly recorded on GNU 201 | ;;; Emacs. Actually, if you used ESC for META (like me), everything 202 | ;;; worked fine. But using ALT for META was broken. 203 | ;;; Now I'm using this-command-keys-vector when I can. 204 | ;;; I also added the dot-mode-event-to-string function to make the 205 | ;;; output a little prettier. 206 | ;;; Thanks to Scott Evans for reporting the bug! 207 | ;;; 208 | ;;; 1.11 209 | ;;; Fixed a bug where dot-mode would give an error if you used 210 | ;;; dot-mode-override to record a and then tried to call 211 | ;;; dot-mode-execute. The bug was in dot-mode-event-to-string 212 | ;;; Thanks to Scott Evans for reporting the bug! 213 | ;;; 214 | ;;; 1.12 215 | ;;; Make calls to make-local-hook optional for Emacs 24 compatibility. 216 | ;;; Use kmacro-display for displaying the macro string. 217 | ;;; 218 | ;;; 1.13 219 | ;;; Misc updates to follow elisp progression and add tests. 220 | ;;; Remove XEmacs compatibility. 221 | 222 | (require 'kmacro) 223 | 224 | (defvar dot-mode-global-mode t 225 | "Should dot-mode share its command buffer between buffers?") 226 | 227 | (defvar dot-mode-ignore-undo t 228 | "Should dot-mode ignore undo?") 229 | 230 | (defvar dot-mode-changed nil 231 | "Did last command change buffer?") 232 | 233 | (defvar dot-mode-cmd-buffer nil 234 | "Saved commands.") 235 | 236 | (defvar dot-mode-cmd-keys nil 237 | "Saved keys.") 238 | 239 | (defvar dot-mode-state 0 240 | "Current state of dot mode. 241 | 0 - Initial (no changes) 242 | 1 - Recording buffer changes 243 | 2 - Override from state 0 244 | 3 - Override from state 1") 245 | 246 | (defvar dot-mode-minibuffer-input nil 247 | "Global buffer to capture minibuffer input") 248 | 249 | (defvar dot-mode-verbose t 250 | "Message the user every time a repeat happens") 251 | 252 | ;; n.b. This is a little tricky ... when the prefix-argument is changed it 253 | ;; doesn't leave much of a trace. It resets `this-command' and 254 | ;; `real-this-command' to the previous ones. 255 | ;; Hence the best way (that I know of) to tell whether the last command was 256 | ;; changing the prefix is by adding a hook into 257 | ;; `prefix-command-preserve-state-hook'. 258 | (defvar dot-mode-prefix-arg nil 259 | "Marker variable to show the prefix argument has been changed.") 260 | 261 | (defvar dot-mode-argument-buffer nil 262 | "Global buffer to store current digit argument.") 263 | 264 | (defun dot-mode-buffer-to-string () 265 | "Return the macro buffer as a string." 266 | (kmacro-display dot-mode-cmd-buffer)) 267 | 268 | (defun dot-mode-minibuffer-exit () 269 | "Catch minibuffer exit" 270 | ;; I'd really like to check `this-command' to see if it's `exit-minibuffer' 271 | ;; and remove this function from the `minibuffer-exit-hook' if it is. 272 | ;; Unfortunately, if an extended command asks for 2 or more arguments, 273 | ;; the first arg would be the only one to get recorded since `exit-minibuffer' 274 | ;; is called between each argument. 275 | (push (minibuffer-contents) dot-mode-minibuffer-input)) 276 | 277 | (defun dot-mode-after-change (start end prevlen) 278 | "Dot mode's `after-change-functions' hook" 279 | ;; By the time we get here, `dot-mode-pre-hook' has already setup 280 | ;; `dot-mode-cmd-keys.' It'll be a `vector', `t', or `nil'. 281 | (cond ((vectorp dot-mode-cmd-keys) 282 | ;; We just did `execute-extended-command' or an override. 283 | ;; If we're in override, the keys have already been read and 284 | ;; `dot-mode-changed' is `t' 285 | (unless dot-mode-changed 286 | (remove-hook 'minibuffer-exit-hook 'dot-mode-minibuffer-exit) 287 | (unless (null dot-mode-minibuffer-input) 288 | ;; The first item in this list is what was in the minibuffer 289 | ;; after choosing the command from either 290 | ;; `execute-extended-command' or `smex'. 291 | ;; This may very well not be the name of the command, so we 292 | ;; replace it with the head of the list 293 | ;; `extended-command-history'. 294 | (setq dot-mode-cmd-keys 295 | (vconcat dot-mode-cmd-keys 296 | (mapconcat 297 | #'identity 298 | (cons (car extended-command-history) 299 | (cdr (nreverse dot-mode-minibuffer-input))) 300 | "\r")))))) 301 | ;; Normal mode 302 | (dot-mode-cmd-keys 303 | (setq dot-mode-cmd-keys (this-command-keys-vector)))) 304 | ;; Else, do nothing `dot-mode-cmd-keys' will remain `nil'. 305 | ;; (Only happens on `ignore-undo') 306 | (when dot-mode-cmd-keys 307 | (setq dot-mode-changed t))) 308 | 309 | (defun dot-mode-pre-hook () 310 | "Dot mode's `pre-command-hook'" 311 | 312 | ;; remove hook (should already be removed... but double check) 313 | ;; The only time this will ever do any good is if you did a 314 | ;; quit out of the minibuffer. In that case, the hook will 315 | ;; still be there. It won't really hurt anything, it will just 316 | ;; continue to record everything you do in the minibuffer 317 | ;; regardless of whether or not it is an `execute-extended-command'. 318 | ;; And the `dot-mode-minibuffer-input' buffer could get quite large. 319 | (remove-hook 'minibuffer-exit-hook 'dot-mode-minibuffer-exit) 320 | 321 | (cond 322 | ;; Is this an `execute-extended-command' or `smex'? 323 | ((member this-command '(execute-extended-command smex)) 324 | (setq dot-mode-minibuffer-input nil 325 | ;; Must get this (M-x) now! It's gone later. 326 | dot-mode-cmd-keys (this-command-keys-vector) 327 | ;; ignore an override 328 | dot-mode-changed nil) 329 | ;; Must be a global hook 330 | (add-hook 'minibuffer-exit-hook 'dot-mode-minibuffer-exit)) 331 | (dot-mode-changed ;; on override, `dot-mode-changed' is t 332 | ;; Always read the keys here on override _UNLESS_ it's a `quoted-insert'. 333 | ;; This is to make sure we capture keys that don't change the buffer. 334 | ;; On `quoted-insert', all we get here is , but in `dot-mode-after-change', 335 | ;; we get  plus the following key (and we're guaranteed to change the 336 | ;; buffer) 337 | (setq dot-mode-cmd-keys (or (eq this-command 'quoted-insert) 338 | (this-command-keys-vector)))) 339 | ;; Should we ignore this key sequence? (is it an undo?) 340 | ((and dot-mode-ignore-undo 341 | (member this-command '(advertised-undo undo undo-tree-undo undo-tree-redo))) 342 | (setq dot-mode-cmd-keys nil)) 343 | ;; signal to read later (in `dot-mode-after-change') 344 | (t (setq dot-mode-cmd-keys t)))) 345 | 346 | ;; (defun dot-mode--state-name () 347 | ;; (nth dot-mode-state '("Initial (no changes)" 348 | ;; "Recording buffer changes" 349 | ;; "Override from recording" 350 | ;; "Override from initial"))) 351 | 352 | (defun dot-mode-prefix-command-hook () (setq dot-mode-prefix-arg t)) 353 | (defun dot-mode-loop () 354 | "The heart of dot mode." 355 | ;; (message "in:\tstate: \"%s\"\n\tcommand: \"%S\"" 356 | ;; (dot-mode--state-name) this-command) 357 | ;; (message "in: cmd-buffer is '%s'" (dot-mode-buffer-to-string)) 358 | 359 | ;; Record all digit-argument and universal-argument functions 360 | (cond (dot-mode-prefix-arg 361 | ;; Keep this keypress around, and don't change the current state 362 | (setq dot-mode-prefix-arg nil 363 | dot-mode-argument-buffer (vconcat dot-mode-argument-buffer (this-command-keys-vector)))) 364 | ((= dot-mode-state 0) ; idle 365 | (if dot-mode-changed 366 | (setq dot-mode-state 1 367 | dot-mode-changed nil 368 | dot-mode-cmd-buffer (vconcat dot-mode-argument-buffer dot-mode-cmd-keys))) 369 | (setq dot-mode-argument-buffer nil)) 370 | ((= dot-mode-state 1) ; recording 371 | (if dot-mode-changed 372 | (setq dot-mode-changed nil 373 | dot-mode-cmd-buffer (vconcat dot-mode-cmd-buffer dot-mode-argument-buffer dot-mode-cmd-keys)) 374 | (setq dot-mode-state 0)) 375 | (setq dot-mode-argument-buffer nil)) 376 | (t ; = 2 or 3 ; override 377 | (setq dot-mode-state (- dot-mode-state 2) 378 | dot-mode-changed t))) 379 | ;; (message "out: state is \"%s\"" (dot-mode--state-name)) 380 | ;; (message "out: cmd-buffer is '%s'" (dot-mode-buffer-to-string)) 381 | ) 382 | 383 | (defun dot-mode-remove-hooks () 384 | (remove-hook 'pre-command-hook 'dot-mode-pre-hook t) 385 | (remove-hook 'post-command-hook 'dot-mode-loop t) 386 | (remove-hook 'after-change-functions 'dot-mode-after-change t) 387 | (remove-hook 'prefix-command-preserve-state-hook 'dot-mode-prefix-command-hook t)) 388 | 389 | (defun dot-mode-add-hooks () 390 | (add-hook 'pre-command-hook 'dot-mode-pre-hook nil t) 391 | (add-hook 'post-command-hook 'dot-mode-loop nil t) 392 | (add-hook 'after-change-functions 'dot-mode-after-change nil t) 393 | (add-hook 'prefix-command-preserve-state-hook 'dot-mode-prefix-command-hook nil t)) 394 | 395 | ;;;###autoload 396 | (defun dot-mode-copy-to-last-kbd-macro () 397 | "Copy the current `dot-mode' command buffer to the `last-kbd-macro' variable. 398 | Then it can be called with `call-last-kbd-macro', named with 399 | `name-last-kbd-macro', or even saved for later use with 400 | `name-last-kbd-macro'" 401 | (interactive) 402 | (if (null dot-mode-cmd-buffer) 403 | (message "Nothing to copy.") 404 | (setq last-kbd-macro dot-mode-cmd-buffer) 405 | (message "Copied."))) 406 | 407 | ;;;###autoload 408 | (defun dot-mode-execute () 409 | "Execute stored commands." 410 | (interactive) 411 | ;; Don't want execution to kick off infinite recursion 412 | (if (null dot-mode-cmd-buffer) 413 | (message "Nothing to repeat") 414 | (dot-mode-remove-hooks) 415 | ;; Do the business 416 | (when dot-mode-verbose 417 | (message "Repeating \"%s\"" (dot-mode-buffer-to-string))) 418 | (condition-case nil 419 | (execute-kbd-macro dot-mode-cmd-buffer) 420 | ((error quit exit) 421 | (setq dot-mode-cmd-buffer nil 422 | dot-mode-state 0) 423 | (message "Dot mode reset"))) 424 | (if (and (not (null dot-mode-cmd-buffer)) 425 | dot-mode-verbose) 426 | ;; I message before AND after a macro execution. 427 | ;; This way you'll know if your macro somehow 428 | ;; hangs during execution. 429 | (message "Repeated \"%s\"" (dot-mode-buffer-to-string))) 430 | ;; Put the hooks back 431 | (dot-mode-add-hooks))) 432 | 433 | ;;;###autoload 434 | (defun dot-mode-override () 435 | "Unconditionally store next keystroke." 436 | (interactive) 437 | (setq dot-mode-state (+ dot-mode-state 2)) 438 | ;; If dot-mode-argument-buffer is non nil then we were in the middle (or at 439 | ;; the end of a argument chain). In that case we take care to not break it. 440 | ;; If it is `nil', then `universal-argument--mode' was not previously active, 441 | ;; and we don't activate it in order to avoid changing behaviour. 442 | ;; n.b. We're checking `dot-mode-argument-buffer' as a variable that happens 443 | ;; to be `nil' when we weren't in an argument chain, it's *not* the "thing 444 | ;; we're interested in". 445 | (when dot-mode-argument-buffer 446 | ;; The docstring of `prefix-command-update' says we need to call it whenever 447 | ;; we change the "prefix command state". 448 | (progn(prefix-command-update) 449 | (setq prefix-arg current-prefix-arg) 450 | (universal-argument--mode))) 451 | (message "dot-mode will remember the next keystroke...")) 452 | 453 | ;;;###autoload 454 | (define-minor-mode dot-mode 455 | "Dot mode mimics the `.' function in vi, repeating sequences of 456 | commands and/or typing delimited by motion events. Use `C-.' 457 | rather than just `.'." nil " Dot" 458 | (let ((map (make-sparse-keymap))) 459 | (define-key map (kbd "C-.") 'dot-mode-execute) 460 | (define-key map (kbd "C-M-.") 'dot-mode-override) 461 | (define-key map (kbd "C-c .") 'dot-mode-copy-to-last-kbd-macro) 462 | map) 463 | (if (not dot-mode) 464 | (dot-mode-remove-hooks) 465 | (dot-mode-add-hooks) 466 | (if dot-mode-global-mode 467 | (progn 468 | (kill-local-variable 'dot-mode-cmd-buffer) 469 | (kill-local-variable 'dot-mode-cmd-keys) 470 | (kill-local-variable 'dot-mode-state) 471 | (kill-local-variable 'dot-mode-changed) 472 | (kill-local-variable 'dot-mode-prefix-arg) 473 | (kill-local-variable 'dot-mode-argument-buffer)) 474 | ;; ELSE 475 | (make-local-variable 'dot-mode-cmd-buffer) 476 | (make-local-variable 'dot-mode-cmd-keys) 477 | (make-local-variable 'dot-mode-state) 478 | (make-local-variable 'dot-mode-changed) 479 | (make-local-variable 'dot-mode-prefix-arg) 480 | (make-local-variable 'dot-mode-argument-buffer) 481 | (setq dot-mode-state 0 482 | dot-mode-changed nil 483 | dot-mode-cmd-buffer nil 484 | dot-mode-cmd-keys nil 485 | dot-mode-prefix-arg nil 486 | dot-mode-argument-buffer nil)))) 487 | 488 | ;;;###autoload 489 | (defun dot-mode-on () 490 | "Turn on dot-mode." 491 | (interactive) 492 | ;; Ignore internal buffers -- this stops modifications in the echo area being 493 | ;; recorded as a macro that gets used elsewhere. 494 | (unless (or (eq ?\ (aref (buffer-name) 0)) 495 | ;; Also ignore the *Messages* buffer -- when `dot-mode' is enabled 496 | ;; here some recursion happens due to the `after-change-functions' 497 | ;; in that buffer getting called. 498 | (eq (current-buffer) (messages-buffer)) 499 | ;; I suspect all minibuffers will have a space at the start of 500 | ;; their buffer name, and hence I won't need this check. 501 | ;; Unfortunately I can't find any documentation 502 | ;; disproving/confirming this, so we include this check. 503 | (minibufferp)) 504 | (dot-mode 1))) 505 | 506 | ;;;###autoload 507 | (defalias 'turn-on-dot-mode 'dot-mode-on) 508 | ;;;###autoload 509 | (define-global-minor-mode global-dot-mode dot-mode dot-mode-on) 510 | 511 | (provide 'dot-mode) 512 | 513 | ;;; dot-mode.el ends here 514 | --------------------------------------------------------------------------------