commit a23fd6fc2e04c68879fe50e6c8cac0ab86c55f7f Author: John Miller Date: Fri Sep 9 09:45:44 2016 -0500 Squashed 'lib/smartrep/' content from commit f0ff5a6 git-subtree-dir: lib/smartrep git-subtree-split: f0ff5a6d7b8603603598ae3045c98b011e58d86e diff --git a/.ert-runner b/.ert-runner new file mode 100644 index 0000000..e35e9c9 --- /dev/null +++ b/.ert-runner @@ -0,0 +1 @@ +-L . diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..8b8129c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +language: emacs-lisp +notifications: + email: false +env: + matrix: + # - EMACS=emacs23 + - EMACS=emacs24 + # - EMACS=emacs-snapshot +before_install: + # PPA for stable Emacs packages + - sudo add-apt-repository -y ppa:cassou/emacs + # PPA for Emacs nightlies + - sudo add-apt-repository -y ppa:ubuntu-elisp/ppa + # Update and install the Emacs for our environment + - sudo apt-get update -qq + - sudo apt-get install -qq -yy ${EMACS}-nox ${EMACS}-el + # Install Cask + - curl -fsSkL --max-time 10 --retry 10 --retry-delay 10 + https://raw.github.com/cask/cask/master/go | python + - export PATH="$HOME/.cask/bin:$PATH" + - cask +script: + - emacs --version + - emacs -Q -batch -eval '(message (system-name))' + - make + \ No newline at end of file diff --git a/Cask b/Cask new file mode 100644 index 0000000..f10fecc --- /dev/null +++ b/Cask @@ -0,0 +1,9 @@ +(source gnu) +(source melpa) + +(package-file "smartrep.el") + +(development + (depends-on "ert-runner") + (depends-on "undercover") + (depends-on "cl-lib")) \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6ca3a60 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +EMACS ?= emacs +CASK ?= cask + +all: + ${MAKE} clean + ${MAKE} test + ${MAKE} clean + +compile: + ${CASK} exec ${EMACS} -Q -batch -L . -eval \ + "(progn \ + (when (version<= \"24.3\" emacs-version) \ + (setq byte-compile-error-on-warn t)) \ + (batch-byte-compile))" smartrep.el +test: + ${CASK} exec ert-runner +clean: + rm -f smartrep.elc + +.PHONY: all compile test clean diff --git a/README.org b/README.org new file mode 100644 index 0000000..256011d --- /dev/null +++ b/README.org @@ -0,0 +1,142 @@ +* Smartrep [[http://melpa.org/#/smartrep][file:http://melpa.org/packages/smartrep-badge.svg]] [[http://stable.melpa.org/#/smartrep][file:http://stable.melpa.org/packages/smartrep-badge.svg]] [[https://travis-ci.org/myuhe/smartrep.el][https://travis-ci.org/myuhe/smartrep.el.svg?branch=master]] [[https://coveralls.io/r/myuhe/smartrep.el?branch=master][https://coveralls.io/repos/myuhe/smartrep.el/badge.svg?branch=master]] + + + =smartrep= is a sequential command interface library. It enables the omittance of typing prefix keys. (e.g., C-c C-n C-n C-n ... instead of C-c C-n C-c C-n C-c C-n ...) + +* Contents + + - [[https://github.com/myuhe/smartrep.el#usage][Usage]] + - [[https://github.com/myuhe/smartrep.el#example][Examples]] + - [[https://github.com/myuhe/smartrep.el#smartrep-works-in-many-applications][Smartrep works in many applications]] + - [[https://github.com/myuhe/smartrep.el#similar-application][Similar application]] + +* Usage + =smartrep= offers only one function =smartrep-define-key=. + The key is bound with =smartrep-define-key= like: + +#+begin_src elisp + (smartrep-define-key + global-map "M-g" + '(("n" . next-line) + ("p" . previous-line))) +#+end_src + +Then after typing =M-g=, =n= exetutes =next-line= and =p= executes =previous-line=. + +If any other key is typed, the special key binding is canceled. That is, if =n= is typed, "n" is inserted as a character. + +* Examples +** Control Two Buffers + Scroll another buffer. The current buffer is not changed. + If you want to scroll by two lines in the other buffer, just type =C-q n n=. +#+begin_src elisp +(smartrep-define-key + global-map "C-q" '(("n" . (scroll-other-window 1)) + ("p" . (scroll-other-window -1)) + ("N" . 'scroll-other-window) + ("P" . (scroll-other-window '-)) + ("a" . (beginning-of-buffer-other-window 0)) + ("e" . (end-of-buffer-other-window 0)))) +#+end_src + +** Change Window Size + Changing Window size tends to execute the same command continuously. It is boring work. + So eval this example and type =C-x { { {=. + +#+begin_src elisp + (smartrep-define-key + global-map "C-x" + '(("{" . shrink-window-horizontally) + ("}" . enlarge-window-horizontally))) +#+end_src + + +** Move header on Org-mode + Org-mode has complicated prefix keys. It is so frustrating. + + This idea simplifies it. If you want to move from heading to heading, then type =C-c= and then =C-n= or =C-p=. + +#+begin_src elisp + (smartrep-define-key + org-mode-map "C-c" '(("C-n" . (outline-next-visible-heading 1)) + ("C-p" . (outline-previous-visible-heading 1)))) +#+end_src + +** Remotely Control Firefox + You may control Firefox via Emacs. + + This example enables the manipulation of Firefox to scroll up, down and more. + + It requires [[https://github.com/bard/mozrepl/blob/master/chrome/content/moz.el][moz.el]] + + + +#+begin_src elisp +(autoload 'moz-minor-mode "moz" "Mozilla Minor and Inferior Mozilla Modes" t) +(moz-minor-mode t) + +(defun moz-send-message (moz-command) + (comint-send-string + (inferior-moz-process) + (concat moz-repl-name ".pushenv('printPrompt', 'inputMode'); " + moz-repl-name ".setenv('inputMode', 'line'); " + moz-repl-name ".setenv('printPrompt', false); undefined; ")) + (comint-send-string + (inferior-moz-process) + (concat moz-command + moz-repl-name ".popenv('inputMode', 'printPrompt'); undefined;\n"))) + +(defun moz-scrolldown-1 () + (interactive) + (moz-send-message "goDoCommand('cmd_scrollLineDown');\n")) + +(defun moz-scrolldown () + (interactive) + (moz-send-message "goDoCommand('cmd_scrollPageDown');")) + +(defun moz-scrollup-1 () + (interactive) + (moz-send-message "goDoCommand('cmd_scrollLineUp');\n")) + +(defun moz-scrollup () + (interactive) + (moz-send-message "goDoCommand('cmd_scrollPageUp');")) + +(defun moz-top () + (interactive) + (moz-send-message "goDoCommand('cmd_scrollTop');\n")) + +(defun moz-bottom () + (interactive) + (moz-send-message "goDoCommand('cmd_scrollBottom');\n")) + +(require 'smartrep) + +(smartrep-define-key + global-map "M-g" '(("n" . moz-scrolldown-1) + ("N" . moz-scrolldown) + ("p" . moz-scrollup-1) + ("P" . moz-scrollup) + ("a" . moz-top) + ("e" . moz-bottom))) +#+end_src + + +* Smartrep Works In Many Applications + Many applications use =smartrep=. Thanks!! + + - [[https://github.com/rolandwalker/back-button][rolandwalker/back-button]] + - [[https://github.com/rolandwalker/fixmee][rolandwalker/fixmee]] + - [[https://github.com/millejoh/emacs-ipython-notebook][millejoh/emacs-ipython-notebook]] + - [[https://github.com/aki2o/owdriver][aki2o/owdriver]] + +* Related article + - [[http://oremacs.com/2015/01/14/repeatable-commands/][Zoom in / out with style · (or emacs]] + - [[http://sheephead.homelinux.org/2011/12/19/6930/][連続操作を素敵にするsmartrep.el作った - sheephead]] (In Japanese) + - [[http://sheephead.homelinux.org/2012/01/30/6934/][smartrep.el 0.0.3をリリースしました - sheephead]] (In Japanese) + - [[http://tam5917.hatenablog.com/entry/20121208/1354931551][multiple-cursors.elのキーバインドを少しだけ改善 - 備忘録]] (In Japanese) + - [[http://d.hatena.ne.jp/rubikitch/20140613/smartrep][Emacs - smartrep.elでrepeatを活性化せよ - http://rubikitch.com/に移転しました]] (In Japanese) + +* Similar Applications + +[[https://github.com/abo-abo/hydra][abo-abo/hydra]] diff --git a/smartrep.el b/smartrep.el new file mode 100644 index 0000000..6dcb895 --- /dev/null +++ b/smartrep.el @@ -0,0 +1,194 @@ +;;; smartrep.el --- Support sequential operation which omitted prefix keys. + +;; Filename: smartrep.el +;; Description: Support sequential operation which omitted prefix keys. +;; Author: myuhe +;; Maintainer: myuhe +;; Copyright (C) :2011,2012 myuhe all rights reserved. +;; Created: :2011-12-19 +;; Version: 1.0.0 +;; Keywords: convenience +;; URL: https://github.com/myuhe/smartrep.el + +;; 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 3, 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., 51 Franklin Street, Fifth Floor, +;; Boston, MA 0:110-1301, USA. + +;;; Commentary: + +;; Installation: +;; Put the smartrep.el to your load-path. +;; And add to .emacs: (require 'smartrep) + +;;; Changelog: + +;; 2012-01-06 Remove unnecessary cord. +;; 2012-01-06 read-key is replaced read-event for compatibility. thanks @tomy_kaira !! +;; 2012-01-11 Support function calling form. (buzztaiki) +;; Call interactively when command. (buzztaiki) +;; Support unquoted function. (buzztaiki) +;; 2012-01-11 new command `smartrep-restore-original-position' `smartrep-quit' (rubikitch) +;; add mode line notification (rubikitch) +;; 2012-01-12 add mode-line-color notification +;; + +;;; Code: +(eval-when-compile + (require 'cl)) + +(defgroup smartrep nil + "Support sequential operation which omitted prefix keys" + :group 'keyboard) + +(defcustom smartrep-mode-line-string-activated "========== SMARTREP ==========" + "Lighter when smartrep-mode is activated" + :type 'string + :group 'smartrep) + +(defcustom smartrep-mode-line-active-bg (face-background 'highlight) + "Background color when smartrep-mode is activated" + :type 'string + :group 'smartrep) + +(defvar smartrep-key-string nil) + +(defvar smartrep-read-event + (if (fboundp 'read-event) 'read-event 'read-key) + "Function to be used for reading keyboard events.") + +(defvar smartrep-mode-line-string nil + "Mode line indicator for smartrep.") + +(defvar smartrep-global-alist-hash (make-hash-table :test 'equal)) + +(defvar smartrep-original-position nil + "A cons holding the point and window-start when smartrep is invoked.") + +(let ((cell (or (memq 'mode-line-position mode-line-format) + (memq 'mode-line-buffer-identification mode-line-format))) + (newcdr 'smartrep-mode-line-string)) + (when cell + (unless (member newcdr mode-line-format) + (setcdr cell (cons newcdr (cdr cell)))))) + +(defun smartrep-define-key (keymap prefix alist) + (when (eq keymap global-map) + (puthash prefix alist smartrep-global-alist-hash)) + (setq alist + (if (eq keymap global-map) + alist + (append alist (gethash prefix smartrep-global-alist-hash)))) + (let ((oa (make-vector 13 nil))) + (mapc (lambda(x) + (let ((obj (intern (prin1-to-string + (smartrep-unquote (cdr x))) + oa))) + (fset obj (smartrep-map alist)) + (define-key keymap + (read-kbd-macro + (concat prefix " " (car x))) obj))) + alist))) +(put 'smartrep-define-key 'lisp-indent-function 2) + +(defun smartrep-map (alist) + (lexical-let ((lst alist)) + (lambda () (interactive) (smartrep-map-internal lst)))) + +(defun smartrep-restore-original-position () + (interactive) + (destructuring-bind (pt . wstart) smartrep-original-position + (goto-char pt) + (set-window-start (selected-window) wstart))) + +(defun smartrep-quit () + (interactive) + (setq smartrep-mode-line-string "") + (smartrep-restore-original-position) + (keyboard-quit)) + +(defun smartrep-map-internal (lst) + (interactive) + (setq smartrep-mode-line-string smartrep-mode-line-string-activated) + (let ((ml-original-bg (face-background 'mode-line))) + (when smartrep-mode-line-active-bg + (set-face-background 'mode-line smartrep-mode-line-active-bg) + (force-mode-line-update)) + (setq smartrep-original-position (cons (point) (window-start))) + (unwind-protect + (let ((repeat-repeat-char last-command-event)) + (smartrep-do-fun repeat-repeat-char lst) + (when repeat-repeat-char + (smartrep-read-event-loop lst))) + (setq smartrep-mode-line-string "") + (when smartrep-mode-line-active-bg + (set-face-background 'mode-line ml-original-bg) + (force-mode-line-update))))) + +(defun smartrep-read-event-loop (lst) + (lexical-let ((undo-inhibit-record-point t)) + (unwind-protect + (while + (lexical-let ((evt (funcall smartrep-read-event))) + ;; (eq (or (car-safe evt) evt) + ;; (or (car-safe repeat-repeat-char) + ;; repeat-repeat-char)) + (setq smartrep-key-string evt) + (smartrep-extract-char evt lst)) + (smartrep-do-fun smartrep-key-string lst))) + (setq unread-command-events (list last-input-event)))) + +(defun smartrep-extract-char (char alist) + (car (smartrep-filter char alist))) + +(defun smartrep-extract-fun (char alist) + (let* ((rawform (cdr (smartrep-filter char alist))) + (form (smartrep-unquote rawform))) + (cond + ((commandp form) + (setq this-command form) + (unwind-protect + (call-interactively form) + (setq last-command form))) + ((functionp form) (funcall form)) + ((and (listp form) (symbolp (car form))) (eval form)) + (t (error "Unsupported form %c %s" char rawform))))) + +(defun smartrep-do-fun (char alist) + (condition-case err + (progn + (run-hooks 'pre-command-hook) + (smartrep-extract-fun char alist) + (run-hooks 'post-command-hook)) + (error + (ding) + (message "%s" (cdr err))))) + + +(defun smartrep-unquote (form) + (if (and (listp form) (memq (car form) '(quote function))) + (eval form) + form)) + +(defun smartrep-filter (char alist) + (loop for (key . form) in alist + for rkm = (read-kbd-macro key) + for number = (if (vectorp rkm) + (aref rkm 0) + (string-to-char rkm)) + if (eq char number) + return (cons number form))) + +(provide 'smartrep) + +;;; smartrep.el ends here diff --git a/test/smartrep-test.el b/test/smartrep-test.el new file mode 100644 index 0000000..650d984 --- /dev/null +++ b/test/smartrep-test.el @@ -0,0 +1,58 @@ +;;; smartrep-test.el --- Test smartrep.el + +;; Copyright (C) 2014 by myuhe all rights reserved. + +;; Author: myuhe +;; URL: https://github.com/myuhe/smartrep.el + +;; 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 3, 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. + +(require 'ert) +(require 'undercover) +(undercover "smartrep.el") + +(require 'smartrep) + +(defun smartrep-test-func (&optional arg) + (or arg 1)) + +(defun smartrep-test-command () + (interactive) + ;; NOTE: In noninteractive(batch) mode, + ;; (called-interactively-p 'interactive) never return `t' + (if (called-interactively-p 'interactive) 2 1)) + +(ert-deftest smartrep-unquote () + "Tests for `smartrep-unquote'" + (should (eq (smartrep-unquote '(quote hoge)) 'hoge)) + (should (eq (smartrep-unquote '(function hoge)) 'hoge)) + (should (eq (smartrep-unquote 'hoge) 'hoge))) + +(ert-deftest smartrep-extract-fun () + "Tests for `smartrep-extract-fun'" + (should (= (smartrep-extract-fun ?a '(("a" . smartrep-test-func))) 1)) + (should (= (smartrep-extract-fun ?a '(("a" . (lambda () (smartrep-test-func))))) 1)) + (should (= (smartrep-extract-fun ?a '(("a" . (smartrep-test-func)))) 1)) + (should (= (smartrep-extract-fun ?a '(("a" . (smartrep-test-func 2)))) 2)) + (unless noninteractive + (should (= (smartrep-extract-fun ?a '(("a" . smartrep-test-command))) 2)))) + +(ert-deftest smartrep-extract-fun-with-quote () + "Test for `smartrep-extract-fun' with quote" + (should (= (smartrep-extract-fun ?a '(("a" . 'smartrep-test-func))) 1)) + (should (= (smartrep-extract-fun ?a '(("a" . '(lambda () (smartrep-test-func))))) 1)) + (should (= (smartrep-extract-fun ?a '(("a" . #'(lambda () (smartrep-test-func))))) 1)) + (should (= (smartrep-extract-fun ?a '(("a" . '(smartrep-test-func)))) 1)) + (should (= (smartrep-extract-fun ?a '(("a" . '(smartrep-test-func 2)))) 2)) + (unless noninteractive + (should (= (smartrep-extract-fun ?a '(("a" . 'smartrep-test-command))) 2)))) + +;;; smartrep-test.el end here