diff --git a/.gitignore b/.gitignore index 433da84..96a152a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .cask *.elc +*-autoloads.el diff --git a/owdriver.el b/owdriver.el index 8f78e72..224d4b9 100644 --- a/owdriver.el +++ b/owdriver.el @@ -1,12 +1,12 @@ -;;; owdriver.el --- Quickly perform various actions on other windows +;;; owdriver.el --- Quickly perform various actions on other windows -*- lexical-binding: t -*- ;; Copyright (C) 2014 Hiroaki Otsu ;; Author: Hiroaki Otsu ;; Keywords: convenience ;; URL: https://github.com/aki2o/owdriver -;; Version: 0.2.0 -;; Package-Requires: ((smartrep "0.0.3") (log4e "0.2.0") (yaxception "0.2.0")) +;; Version: 0.3.1 +;; Package-Requires: ((log4e "0.4.1") (yaxception "1.0.0")) ;; 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 @@ -27,84 +27,13 @@ ;; other windows quickly in multi window situation. ;; In default, that's move, scroll and isearch. ;; Moreover, you can add the action what you want. - -;;; Dependency: -;; -;; - smartrep.el ( see ) -;; - yaxception.el ( see ) -;; - log4e.el ( see ) - -;;; Installation: -;; -;; Put this to your load-path. -;; And put the following lines in your .emacs or site-start.el file. -;; -;; (require 'owdriver) - -;;; Configuration: -;; -;; ;; Make config suit for you. About the config item, see Customization or eval the following sexp. -;; ;; (customize-group "owdriver") -;; -;; ;; If you want to do the default config -;; (owdriver-config-default) -;; -;; (owdriver-mode 1) - -;;; Customization: -;; -;; [EVAL] (autodoc-document-lisp-buffer :type 'user-variable :prefix "owdriver-[^-]" :docstring t) -;; `owdriver-prefix-key' -;; String of the prefix keystroke for `owdriver-mode-map'. -;; `owdriver-next-window-prefer-pophint' -;; Whether to prefer to use `pophint:do' for `owdriver-next-window'. -;; -;; *** END auto-documentation - -;;; API: -;; -;; [EVAL] (autodoc-document-lisp-buffer :type 'command :prefix "owdriver-[^-]" :docstring t) -;; `owdriver-next-window' -;; Change the window of `owdriver--window'. -;; `owdriver-previous-window' -;; Change the window of `owdriver--window'. -;; `owdriver-focus-window' -;; Quit driving `owdriver--window' and move to `owdriver--window'. -;; `owdriver-quit' -;; Quit driving `owdriver--window'. -;; -;; *** END auto-documentation -;; [EVAL] (autodoc-document-lisp-buffer :type 'macro :prefix "owdriver-[^-]" :docstring t) -;; `owdriver-define-command' -;; Define the command for driving `owdriver--window' from COMMAND. -;; -;; *** END auto-documentation -;; [EVAL] (autodoc-document-lisp-buffer :type 'function :prefix "owdriver-[^-]" :docstring t) -;; `owdriver-add-keymap' -;; Add the keymap of `owdriver-mode-map'. -;; `owdriver-config-default' -;; Do the recommended configuration. -;; -;; *** END auto-documentation -;; [Note] Functions and variables other than listed above, Those specifications may be changed without notice. - -;;; Tested On: -;; -;; - Emacs ... GNU Emacs 26.1 (build 1, x86_64-apple-darwin14.5.0, NS appkit-1348.17 Version 10.10.5 (Build 14F2511)) of 2018-05-31 -;; - smartrep.el ... Version 0.0.3 -;; - yaxception.el ... Version 0.2.0 -;; - log4e.el ... Version 0.2.0 - - ;; Enjoy!!! +;;; Code: -(eval-when-compile (require 'cl)) -(require 'smartrep) +(eval-when-compile (require 'cl-lib)) (require 'log4e) (require 'yaxception) -(require 'inertial-scroll nil t) -(require 'pophint nil t) (defgroup owdriver nil "Quickly perform various actions on other windows." @@ -117,10 +46,36 @@ :group 'owdriver) (defcustom owdriver-next-window-prefer-pophint t - "Whether to prefer to use `pophint:do' for `owdriver-next-window'." + "Whether to prefer to use `pophint:do' for `owdriver-find-next-window'." :type 'boolean :group 'owdriver) +(defcustom owdriver-next-window-function 'owdriver-find-next-window + "Function to find a next window handled by owdriver." + :type 'function + :group 'owdriver) + +(defcustom owdriver-keep-driving-commands '(owdriver-start owdriver-next-window owdriver-previous-window) + "List of command kept handling by `owdriver-keep-driving-p'." + :type (list 'function) + :group 'owdriver) + +(defcustom owdriver-keep-driving-command-prefixes '("scroll-" "next-" "previous-" "forward-" "backward-" "beginning-of-" "end-of-" "move-" "switch-to-" "xref-" "find-" "isearch-" "project-" "projectile-") + "List of command prefix kept handling by `owdriver-keep-driving-p'. +This will be ignored if set `owdriver-keep-driving-command-regexp' non-nil." + :type (list 'string) + :group 'owdriver) + +(defcustom owdriver-keep-driving-command-regexp nil + "Regexp for matching commands kept handling by `owdriver-keep-driving-p'." + :type 'regexp + :group 'owdriver) + +(defcustom owdriver-keep-driving-function 'owdriver-keep-driving-p + "Function to judge to keep handling by owdriver." + :type 'function + :group 'owdriver) + (log4e:deflogger "owdriver" "%t [%l] %m" "%H:%M:%S" '((fatal . "fatal") (error . "error") @@ -132,14 +87,43 @@ (defvar owdriver--window nil "Current window drived by the command of `owdriver-mode-map'.") -(defvar owdriver--move-window-amount nil) -(defvar owdriver--keymap-alist nil) +(defvar owdriver--start-location nil) +(defvar owdriver--keep-driving-function nil) + + +;;;;;;;;;; +;; Mode + +;;;###autoload +(defvar owdriver-mode-map (make-sparse-keymap)) + +;;;###autoload +(define-minor-mode owdriver-mode + "Quickly perform various actions on other windows." + :init-value nil + :lighter " OW" + :keymap owdriver-mode-map + :global t + :group 'owdriver + (if owdriver-mode + (progn + (setq owdriver--start-location `(:window ,(selected-window) :point ,(window-point), :config ,(current-window-configuration))) + (setq owdriver--keep-driving-function owdriver-keep-driving-function) + (add-hook 'pre-command-hook 'owdriver--cleanup)) + (when owdriver--start-location + (when (not (window-live-p (plist-get owdriver--start-location :window))) + (set-window-configuration (plist-get owdriver--start-location :config))) + (select-window (plist-get owdriver--start-location :window)) + (set-window-point (plist-get owdriver--start-location :window) (plist-get owdriver--start-location :point)) + (setq owdriver--start-location nil)) + (setq owdriver--keep-driving-function nil) + (remove-hook 'pre-command-hook 'owdriver--cleanup))) ;;;;;;;;;;;;; ;; Utility -(defun* owdriver--show-message (msg &rest args) +(cl-defun owdriver--show-message (msg &rest args) (apply 'message (concat "[OWDRIVER] " msg) args) nil) @@ -147,130 +131,121 @@ (declare (indent 1)) `(let ((it ,test)) (when it ,@body))) -(defmacro owdriver--with-selected-window (tasknm force-next-window &rest body) +(defmacro owdriver--with-selected-window (command force-next-window &rest body) (declare (indent 2)) `(yaxception:$ (yaxception:try (owdriver--trace "start with select window : wnd[%s] force-next-window[%s]" owdriver--window ,force-next-window) - (when (or ,force-next-window - (not (window-live-p owdriver--window)) - (eq owdriver--window (nth 0 (get-buffer-window-list)))) - (let ((owdriver--move-window-amount 1)) - (owdriver-next-window))) - (with-selected-window owdriver--window + (let ((owdriver-keep-driving-function nil)) + (owdriver-start ,force-next-window) ,@body)) (yaxception:catch 'error e - (owdriver--show-message "Failed %s : %s" ,tasknm (yaxception:get-text e)) + (owdriver--show-message "Failed %s : %s" ',command (yaxception:get-text e)) (owdriver--error "failed %s : %s\n%s" - ,tasknm + ',command (yaxception:get-text e) (yaxception:get-stack-trace-string e))))) (defun owdriver--get-binding-keys (cmd) (owdriver--trace "start get binding keys : %s" cmd) - (loop for b in (where-is-internal cmd global-map) - for bindkey = (or (ignore-errors (key-description b)) - "") - if (and (not (string= bindkey "")) - (not (string-match "\\`" bindkey)) - (not (string-match "\\`<[^>]*mouse[^>]*>" bindkey))) - collect (progn (owdriver--trace "found binding : %s" bindkey) - bindkey))) + (cl-loop for b in (where-is-internal cmd global-map) + for bindkey = (or (ignore-errors (key-description b)) + "") + if (and (not (string= bindkey "")) + (not (string-match "\\`" bindkey)) + (not (string-match "\\`<[^>]*mouse[^>]*>" bindkey))) + collect (progn (owdriver--trace "found binding : %s" bindkey) + bindkey))) (defun owdriver--get-keybind (cmd) (owdriver--trace "start get keybind : %s" cmd) - (loop with ret = nil - for k in (owdriver--get-binding-keys cmd) - if (or (not ret) - (< (length k) (length ret))) - do (setq ret k) - finally return (progn (owdriver--trace "got keybind : %s" ret) - ret))) + (cl-loop with ret = nil + for k in (owdriver--get-binding-keys cmd) + if (or (not ret) + (< (length k) (length ret))) + do (setq ret k) + finally return (progn (owdriver--trace "got keybind : %s" ret) + ret))) + +(defun owdriver--cleanup () + (when (and owdriver-mode + (or (not (functionp owdriver--keep-driving-function)) + (not (funcall owdriver--keep-driving-function this-command)))) + (owdriver--trace "start cleanup. this-command[%s]" this-command) + (owdriver-mode 0))) + + +;;;;;;;;;;;;;; +;; Function + +(defun owdriver-find-next-window (reverse) + (let* ((actwnd (get-buffer-window)) + (currwnd (if (window-live-p owdriver--window) owdriver--window actwnd)) + (is-nextable-window (lambda (w) + (and (window-live-p w) + (not (eq w actwnd)) + (not (eq w currwnd)) + (not (minibufferp (window-buffer w))))))) + (or (and owdriver-next-window-prefer-pophint + (featurep 'pophint) + (boundp 'pophint--next-window-source) + (>= (cl-loop for w in (window-list) count (funcall is-nextable-window w)) 2) + (with-no-warnings + (when-let ((hint (pophint:do :source pophint--next-window-source :allwindow t))) + (pophint:hint-window hint)))) + (progn + (select-window currwnd) + (other-window (if reverse -1 1)) + (selected-window))))) + +(defun owdriver-keep-driving-p (command) + (when (not owdriver-keep-driving-command-regexp) + (setq owdriver-keep-driving-command-regexp + (rx-to-string `(and bos (regexp ,(regexp-opt owdriver-keep-driving-command-prefixes)))))) + (or (memq command owdriver-keep-driving-commands) + (string-match owdriver-keep-driving-command-regexp (symbol-name command)))) -;;;;;;;;;; -;; Mode +;;;;;;;;;;;;;;;;;; +;; User Command ;;;###autoload -(defvar owdriver-mode-map (make-sparse-keymap)) +(defun owdriver-start (&optional force-next-window) + "Start driving the window of `owdriver--window'." + (interactive) + (owdriver-mode 1) + (when (or force-next-window + (not (window-live-p owdriver--window))) + (owdriver-next-window)) + (when (not (eq (selected-window) owdriver--window)) + (select-window owdriver--window))) ;;;###autoload -(define-minor-mode owdriver-mode - "Quickly perform various actions on other windows." - :init-value nil - :lighter " OW" - :keymap owdriver-mode-map - :global t - :group 'owdriver - (smartrep-define-key owdriver-mode-map owdriver-prefix-key owdriver--keymap-alist)) - - -;;;;;;;;;;;;;;;;;; -;; User Command - (defun owdriver-next-window (&optional reverse) "Change the window of `owdriver--window'." (interactive) (yaxception:$ (yaxception:try - (let* ((actwnd (get-buffer-window)) - (currwnd (if (window-live-p owdriver--window) owdriver--window actwnd)) - (move-amount (or owdriver--move-window-amount - (when (window-live-p owdriver--window) 1) - 2)) - (is-nextable-window (lambda (w) - (and (window-live-p w) - (not (eq w actwnd)) - (not (eq w currwnd)) - (not (minibufferp (window-buffer w)))))) - nextwnd popwnd wndloc) - (select-window currwnd) - (owdriver--trace "start %s window. currwnd[%s] move-amount[%s]" - (if reverse "previous" "next") (selected-window) move-amount) - ;; Move to next target window - (if (and (and owdriver-next-window-prefer-pophint - (featurep 'pophint) - (boundp 'pophint--next-window-source) - (>= (loop for w in (window-list) count (funcall is-nextable-window w)) 2))) - (setq nextwnd (pophint:do :source pophint--next-window-source :allwindow t)) - (while (and (> move-amount 0) - (not (eq nextwnd currwnd))) - (other-window (if reverse -1 1)) - (setq nextwnd (get-buffer-window)) - (owdriver--trace "selected next window : %s" nextwnd) - (when (funcall is-nextable-window nextwnd) - (decf move-amount) - (owdriver--trace "decremented move-amount[%s]" move-amount)))) - ;; Blink target window after move - (when (not (eq nextwnd currwnd)) - (owdriver--trace "start blink window : %s" nextwnd) - (let ((ov (make-overlay (window-start) (window-end)))) - (yaxception:$ - (yaxception:try - (overlay-put ov 'face 'highlight) - (select-window actwnd) - (sit-for 0.1) - (select-window nextwnd)) - (yaxception:catch 'error e - (yaxception:throw e)) - (yaxception:finally - (delete-overlay ov))))) - ;; Return to working window at last - (select-window actwnd) - (setq owdriver--window nextwnd) + (setq owdriver--window (funcall owdriver-next-window-function reverse)) + (select-window owdriver--window) + (let ((ov (make-overlay (point-min) (point-max)))) + (overlay-put ov 'face 'highlight) + (run-with-idle-timer 0.1 nil (lambda () (when ov (delete-overlay ov)))) (owdriver--show-message "Drived window is '%s'" owdriver--window))) (yaxception:catch 'error e (owdriver--show-message "Failed next window : %s" (yaxception:get-text e)) (owdriver--error "failed next window : %s\n%s" - (yaxception:get-text e) - (yaxception:get-stack-trace-string e))))) + (yaxception:get-text e) + (yaxception:get-stack-trace-string e))))) +;;;###autoload (defun owdriver-previous-window () "Change the window of `owdriver--window'." (interactive) (owdriver-next-window t)) +;;;###autoload (defun owdriver-focus-window () "Quit driving `owdriver--window' and move to `owdriver--window'." (interactive) @@ -278,6 +253,7 @@ (select-window owdriver--window) (keyboard-quit))) +;;;###autoload (defun owdriver-quit () "Quit driving `owdriver--window'." (interactive) @@ -294,85 +270,61 @@ (when (and (stringp keystroke) (not (string= keystroke "")) (commandp command)) - (define-key owdriver-mode-map - (read-kbd-macro (concat owdriver-prefix-key " " keystroke)) - command) - (owdriver--awhen (assoc keystroke owdriver--keymap-alist) - (setq owdriver--keymap-alist (delq it owdriver--keymap-alist))) - (add-to-list 'owdriver--keymap-alist `(,keystroke . ,command)))) + (define-key owdriver-mode-map (read-kbd-macro keystroke) command))) ;;;###autoload -(defmacro owdriver-define-command (command add-keymap &rest body) +(defmacro owdriver-define-command (command &rest body) "Define the command for driving `owdriver--window' from COMMAND. The command named `owdriver-do-COMMAND' is defined by this function. -ADD-KEYMAP is boolean. If non-nil, do `owdriver-add-keymap' using the key bound to COMMAND in `global-map'. -BODY is sexp. If COMMAND is used in `owdriver--window' actually, this value is no need." +BODY is sexp. If nil, COMMAND will be called." (declare (indent 2)) (let* ((body (or body `((call-interactively ',command)))) (cmdnm (symbol-name command)) (ncommand (intern (concat "owdriver-do-" cmdnm))) - (fcommand (intern (concat "owdriver-do-" cmdnm "-on-next-window"))) - (tasknm (replace-regexp-in-string "-" " " cmdnm))) + (fcommand (intern (concat "owdriver-do-" cmdnm "-on-next-window")))) `(progn - (owdriver--trace "start define command[%s]. add-keymap[%s]" ,cmdnm ,add-keymap) + (owdriver--trace "start define command[%s]" ,cmdnm) ;;;###autoload (defun ,ncommand (&optional arg) ,(format "Do `%s' in `owdriver--window'.\n\nIf prefix argument is given, do `owdriver-next-window' before that." cmdnm) (interactive "p") (let ((force-next-window (and arg (> arg 1)))) - (owdriver--with-selected-window ,tasknm force-next-window + (owdriver--with-selected-window ,ncommand force-next-window ,@body))) + ;;;###autoload (defun ,fcommand () - ,(format "Do `%s' in `owdriver--window' with `owdriver-next-window'." cmdnm) + ,(format "Do `%s' in `owdriver--window'\nwith `owdriver-next-window'." cmdnm) (interactive) - (owdriver--with-selected-window ,tasknm t - ,@body)) - (when ,add-keymap - (dolist (k (owdriver--get-binding-keys ',command)) - (owdriver-add-keymap k ',ncommand)))))) + (owdriver--with-selected-window ,fcommand t + ,@body))))) ;;;###autoload (defun owdriver-config-default () "Do the recommended configuration." ;; Own command - (owdriver-add-keymap "C-o" 'owdriver-next-window) - (owdriver-add-keymap "C-S-o" 'owdriver-previous-window) - (owdriver-add-keymap "" 'owdriver-focus-window) - (owdriver-add-keymap "" 'owdriver-quit) - (owdriver-add-keymap "C-q" 'owdriver-quit) + (owdriver-add-keymap "C-o" 'owdriver-next-window) + (owdriver-add-keymap "C-S-o" 'owdriver-previous-window) + (owdriver-add-keymap "C-c C-k" 'owdriver-focus-window) + (owdriver-add-keymap "C-c C-c" 'owdriver-quit) ;; Basic command - (owdriver-define-command newline t (owdriver-quit)) - (owdriver-define-command scroll-up t) - (owdriver-define-command scroll-up-command t) - (owdriver-define-command scroll-down t) - (owdriver-define-command scroll-down-command t) - (owdriver-define-command scroll-left t (scroll-left 10 t)) - (owdriver-define-command scroll-right t (scroll-right 10 t)) - (owdriver-define-command next-line t) - (owdriver-define-command previous-line t) - (owdriver-define-command forward-char t) - (owdriver-define-command forward-word t) - (owdriver-define-command backward-char t) - (owdriver-define-command backward-word t) - (owdriver-define-command move-beginning-of-line t) - (owdriver-define-command move-end-of-line t) - (owdriver-define-command beginning-of-buffer t) - (owdriver-define-command end-of-buffer t) - (owdriver-define-command isearch-forward t (isearch-forward)) - (owdriver-define-command isearch-backward t (isearch-backward)) - (owdriver-define-command set-mark-command t) - (owdriver-define-command kill-ring-save t (call-interactively 'kill-ring-save) (deactivate-mark)) - ;; Third party command - (owdriver-define-command pophint:do t (pophint:do :not-switch-window t)) - (owdriver-define-command inertias-up t) - (owdriver-define-command inertias-down t) + (owdriver-define-command scroll-up) + (owdriver-define-command scroll-up-command) + (owdriver-define-command scroll-down) + (owdriver-define-command scroll-down-command) + (owdriver-define-command scroll-left (scroll-left 10 t)) + (owdriver-define-command scroll-right (scroll-right 10 t)) + (owdriver-define-command beginning-of-buffer) + (owdriver-define-command end-of-buffer) ;; Patch for Emacs 26.1 (when (>= emacs-major-version 26) (defun owdriver--patch-on-26-1 () - "Function to patch the trouble on GNU Emacs 26.1 (build 1, x86_64-apple-darwin14.5.0, NS appkit-1348.17 Version 10.10.5 (Build 14F2511)) of 2018-05-31, -which emacs seems to not refresh a screen when `scroll-left', `scroll-right' with `with-selected-window'." + "Function to patch the trouble on GNU Emacs 26.1 +(build 1, x86_64-apple-darwin14.5.0, NS appkit-1348.17 Version 10.10.5 +(Build 14F2511)) of 2018-05-31, +which emacs seems to not refresh a screen +when `scroll-left', `scroll-right' with `with-selected-window'." (let ((wnd (get-buffer-window)) (pt (with-selected-window owdriver--window (point)))) (select-window owdriver--window)