diff options
| -rw-r--r-- | Makefile | 46 | ||||
| -rw-r--r-- | README.md | 50 | ||||
| -rw-r--r-- | with-editor.el | 722 | ||||
| -rw-r--r-- | with-editor.info | 323 | ||||
| -rw-r--r-- | with-editor.org | 258 | ||||
| -rw-r--r-- | with-editor.texi | 314 |
6 files changed, 1713 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d92146a --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +ELS = with-editor.el + +DEPS = dash + +INFOPAGES = with-editor.info +TEXIPAGES = with-editor.texi + +ELCS = $(ELS:.el=.elc) +DFLAGS = $(addprefix -L ../,$(DEPS)) +EFLAGS ?= $(DFLAGS) +EMACS ?= emacs +BATCH = $(EMACS) -batch -Q -L . $(EFLAGS) + +MAKEINFO ?= makeinfo +INSTALL_INFO ?= $(shell command -v ginstall-info || printf install-info) + +.PHONY: help clean + +help: + $(info make all - compile elisp and manual) + $(info make lisp - compile elisp) + $(info make info - generate info manual) + $(info make clean - remove generated files) + @printf "\n" + +all: lisp info + +lisp: $(ELCS) +%.elc: %.el + @printf "Compiling %s\n" $< + @$(BATCH)\ + --eval '(setq with-editor-emacsclient-executable nil)'\ + -f batch-byte-compile $< + +info: $(INFOPAGES) dir +%.info: %.texi + @printf "Generating $@\n" + @$(MAKEINFO) --no-split $< -o $@ + +dir: $(TEXIPAGES) + @printf "Generating dir\n" + @echo $^ | xargs -n 1 $(INSTALL_INFO) --dir=$@ + +clean: + @printf "Cleaning...\n" + @rm -f $(ELCS) diff --git a/README.md b/README.md new file mode 100644 index 0000000..84dfa77 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +With-Editor +=========== + +This library makes it possible to reliably use the Emacsclient as +the `$EDITOR` of child processes. It makes sure that they know how +to call home. For remote processes a substitute is provided, which +communicates with Emacs on standard output/input instead of using a +socket as the Emacsclient does. + +It provides the commands `with-editor-async-shell-command` and +`with-editor-shell-command`, which are intended as replacements +for `async-shell-command` and `shell-command`. They automatically +export `$EDITOR` making sure the executed command uses the current +Emacs instance as "the editor". With a prefix argument these +commands prompt for an alternative environment variable such as +`$GIT_EDITOR`. To always use these variants add this to your init +file: + + (define-key (current-global-map) + [remap async-shell-command] 'with-editor-async-shell-command) + (define-key (current-global-map) + [remap shell-command] 'with-editor-shell-command) + +Alternatively use the global `shell-command-with-editor-mode`, +which always sets `$EDITOR` for all Emacs commands which ultimately +use `shell-command` to asynchronously run some shell command. + +The command `with-editor-export-editor` exports `$EDITOR` or +another such environment variable in `shell-mode`, `term-mode` and +`eshell-mode` buffers. Use this Emacs command before executing a +shell command which needs the editor set, or always arrange for the +current Emacs instance to be used as editor by adding it to the +appropriate mode hooks: + + (add-hook 'shell-mode-hook 'with-editor-export-editor) + (add-hook 'term-mode-hook 'with-editor-export-editor) + (add-hook 'eshell-mode-hook 'with-editor-export-editor) + +Some variants of this function exist, these two forms are +equivalent: + + (add-hook 'shell-mode-hook + (apply-partially 'with-editor-export-editor "GIT_EDITOR")) + (add-hook 'shell-mode-hook 'with-editor-export-git-editor) + +This library can also be used by other packages which need to use +the current Emacs instance as editor. In fact this library was +written for Magit and its `git-commit-mode` and `git-rebase-mode`. +Consult `git-rebase.el` and the related code in `magit-sequence.el` +for a simple example. diff --git a/with-editor.el b/with-editor.el new file mode 100644 index 0000000..a9abedc --- /dev/null +++ b/with-editor.el @@ -0,0 +1,722 @@ +;;; with-editor.el --- Use the Emacsclient as $EDITOR -*- lexical-binding: t -*- + +;; Copyright (C) 2014-2016 The Magit Project Contributors + +;; Author: Jonas Bernoulli <jonas@bernoul.li> +;; Maintainer: Jonas Bernoulli <jonas@bernoul.li> + +;; Package-Requires: ((emacs "24.4") (async "1.5") (dash "2.12.1")) +;; Keywords: tools +;; Homepage: https://github.com/magit/with-editor + +;; This file is not part of GNU Emacs. + +;; 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 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 this file. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This library makes it possible to reliably use the Emacsclient as +;; the `$EDITOR' of child processes. It makes sure that they know how +;; to call home. For remote processes a substitute is provided, which +;; communicates with Emacs on standard output/input instead of using a +;; socket as the Emacsclient does. + +;; It provides the commands `with-editor-async-shell-command' and +;; `with-editor-shell-command', which are intended as replacements +;; for `async-shell-command' and `shell-command'. They automatically +;; export `$EDITOR' making sure the executed command uses the current +;; Emacs instance as "the editor". With a prefix argument these +;; commands prompt for an alternative environment variable such as +;; `$GIT_EDITOR'. To always use these variants add this to your init +;; file: +;; +;; (define-key (current-global-map) +;; [remap async-shell-command] 'with-editor-async-shell-command) +;; (define-key (current-global-map) +;; [remap shell-command] 'with-editor-shell-command) + +;; Alternatively use the global `shell-command-with-editor-mode', +;; which always sets `$EDITOR' for all Emacs commands which ultimately +;; use `shell-command' to asynchronously run some shell command. + +;; The command `with-editor-export-editor' exports `$EDITOR' or +;; another such environment variable in `shell-mode', `term-mode' and +;; `eshell-mode' buffers. Use this Emacs command before executing a +;; shell command which needs the editor set, or always arrange for the +;; current Emacs instance to be used as editor by adding it to the +;; appropriate mode hooks: +;; +;; (add-hook 'shell-mode-hook 'with-editor-export-editor) +;; (add-hook 'term-mode-hook 'with-editor-export-editor) +;; (add-hook 'eshell-mode-hook 'with-editor-export-editor) + +;; Some variants of this function exist, these two forms are +;; equivalent: +;; +;; (add-hook 'shell-mode-hook +;; (apply-partially 'with-editor-export-editor "GIT_EDITOR")) +;; (add-hook 'shell-mode-hook 'with-editor-export-git-editor) + +;; This library can also be used by other packages which need to use +;; the current Emacs instance as editor. In fact this library was +;; written for Magit and its `git-commit-mode' and `git-rebase-mode'. +;; Consult `git-rebase.el' and the related code in `magit-sequence.el' +;; for a simple example. + +;;; Code: + +(require 'cl-lib) +(require 'dash) +(require 'server) +(require 'tramp) +(require 'tramp-sh nil t) + +(and (require 'async-bytecomp nil t) + (memq 'magit (bound-and-true-p async-bytecomp-allowed-packages)) + (fboundp 'async-bytecomp-package-mode) + (async-bytecomp-package-mode 1)) + +(eval-when-compile + (progn (require 'dired nil t) + (require 'eshell nil t) + (require 'term nil t) + (require 'warnings nil t))) +(declare-function dired-get-filename 'dired) +(declare-function term-emulate-terminal 'term) +(defvar eshell-preoutput-filter-functions) + +;;; Options + +(defgroup with-editor nil + "Use the Emacsclient as $EDITOR." + :group 'external + :group 'server) + +(defun with-editor-locate-emacsclient () + "Search for a suitable Emacsclient executable." + (--if-let (with-editor-locate-emacsclient-1 (with-editor-emacsclient-path) 3) + it + (display-warning 'with-editor (format "\ +Cannot determine a suitable Emacsclient + +Determining an Emacsclient executable suitable for the +current Emacs instance failed. For more information +please see https://github.com/magit/magit/wiki/Emacsclient.")) + nil)) + +(defun with-editor-locate-emacsclient-1 (path depth) + (let* ((version-lst (-take depth (split-string emacs-version "\\."))) + (version-reg (concat "^" (mapconcat #'identity version-lst "\\.")))) + (or (locate-file-internal + "emacsclient" path + (cl-mapcan + (lambda (v) (cl-mapcar (lambda (e) (concat v e)) exec-suffixes)) + (nconc (cl-mapcon (lambda (v) + (setq v (mapconcat #'identity (reverse v) ".")) + (list v (concat "-" v) (concat ".emacs" v))) + (reverse version-lst)) + (list "" "-snapshot"))) + (lambda (exec) + (ignore-errors + (string-match-p version-reg + (with-editor-emacsclient-version exec))))) + (and (> depth 1) + (with-editor-locate-emacsclient-1 path (1- depth)))))) + +(defun with-editor-emacsclient-version (exec) + (-when-let (1st-line (car (process-lines exec "--version"))) + (cadr (split-string 1st-line)))) + +(defun with-editor-emacsclient-path () + (let ((path exec-path)) + (when invocation-directory + (push (directory-file-name invocation-directory) path) + (let* ((linkname (expand-file-name invocation-name invocation-directory)) + (truename (file-chase-links linkname))) + (unless (equal truename linkname) + (push (directory-file-name (file-name-directory truename)) path))) + (when (eq system-type 'darwin) + (let ((dir (expand-file-name "bin" invocation-directory))) + (when (file-directory-p dir) + (push dir path))) + (when (string-match-p "Cellar" invocation-directory) + (let ((dir (expand-file-name "../../../bin" invocation-directory))) + (when (file-directory-p dir) + (push dir path)))))) + (cl-remove-duplicates path :test 'equal))) + +(defcustom with-editor-emacsclient-executable (with-editor-locate-emacsclient) + "The Emacsclient executable used by the `with-editor' macro." + :group 'with-editor + :type '(choice (string :tag "Executable") + (const :tag "Don't use Emacsclient" nil))) + +(defcustom with-editor-sleeping-editor "\ +sh -c '\ +echo \"WITH-EDITOR: $$ OPEN $0\"; \ +sleep 604800 & sleep=$!; \ +trap \"kill $sleep; exit 0\" USR1; \ +trap \"kill $sleep; exit 1\" USR2; \ +wait $sleep'" + "The sleeping editor, used when the Emacsclient cannot be used. + +This fallback is used for asynchronous process started inside the +macro `with-editor', when the process runs on a remote machine or +for local processes when `with-editor-emacsclient-executable' is +nil (i.e. when no suitable Emacsclient was found, or the user +decided not to use it). + +Where the latter uses a socket to communicate with Emacs' server, +this substitute prints edit requests to its standard output on +which a process filter listens for such requests. As such it is +not a complete substitute for a proper Emacsclient, it can only +be used as $EDITOR of child process of the current Emacs instance." + :group 'with-editor + :type 'string) + +(defcustom with-editor-finish-query-functions nil + "List of functions called to query before finishing session. + +The buffer in question is current while the functions are called. +If any of them returns nil, then the session is not finished and +the buffer is not killed. The user should then fix the issue and +try again. The functions are called with one argument. If it is +non-nil then that indicates that the user used a prefix argument +to force finishing the session despite issues. Functions should +usually honor that and return non-nil." + :group 'with-editor + :type 'hook) +(put 'with-editor-finish-query-functions 'permanent-local t) + +(defcustom with-editor-cancel-query-functions nil + "List of functions called to query before canceling session. + +The buffer in question is current while the functions are called. +If any of them returns nil, then the session is not canceled and +the buffer is not killed. The user should then fix the issue and +try again. The functions are called with one argument. If it is +non-nil then that indicates that the user used a prefix argument +to force canceling the session despite issues. Functions should +usually honor that and return non-nil." + :group 'with-editor + :type 'hook) +(put 'with-editor-cancel-query-functions 'permanent-local t) + +(defcustom with-editor-mode-lighter " WE" + "The mode-line lighter of the With-Editor mode." + :group 'with-editor + :type '(choice (const :tag "No lighter" "") string)) + +(defvar with-editor-server-window-alist nil + "Alist of filename patterns vs corresponding `server-window'. + +Each element looks like (REGEXP . FUNCTION). Files matching +REGEXP are selected using FUNCTION instead of the default in +`server-window'. + +Note that when a package adds an entry here then it probably +has a reason to disrespect `server-window' and it likely is +not a good idea to change such entries.") + +;;; Mode Commands + +(defvar with-editor-pre-finish-hook nil) +(defvar with-editor-pre-cancel-hook nil) +(defvar with-editor-post-finish-hook nil) +(defvar with-editor-post-finish-hook-1 nil) +(defvar with-editor-post-cancel-hook nil) +(defvar with-editor-post-cancel-hook-1 nil) +(defvar with-editor-cancel-alist nil) +(put 'with-editor-pre-finish-hook 'permanent-local t) +(put 'with-editor-pre-cancel-hook 'permanent-local t) +(put 'with-editor-post-finish-hook 'permanent-local t) +(put 'with-editor-post-cancel-hook 'permanent-local t) + +(defvar with-editor-show-usage t) +(defvar with-editor-cancel-message nil) +(defvar with-editor-previous-winconf nil) +(make-variable-buffer-local 'with-editor-show-usage) +(make-variable-buffer-local 'with-editor-cancel-message) +(make-variable-buffer-local 'with-editor-previous-winconf) +(put 'with-editor-cancel-message 'permanent-local t) +(put 'with-editor-previous-winconf 'permanent-local t) + +(defvar-local with-editor--pid nil "For internal use.") +(put 'with-editor--pid 'permanent-local t) + +(defun with-editor-finish (force) + "Finish the current edit session." + (interactive "P") + (when (run-hook-with-args-until-failure + 'with-editor-finish-query-functions force) + (let ((with-editor-post-finish-hook-1 + (ignore-errors (delq t with-editor-post-finish-hook)))) + (run-hooks 'with-editor-pre-finish-hook) + (with-editor-return nil) + (accept-process-output nil 0.1) + (run-hooks 'with-editor-post-finish-hook-1)))) + +(defun with-editor-cancel (force) + "Cancel the current edit session." + (interactive "P") + (when (run-hook-with-args-until-failure + 'with-editor-cancel-query-functions force) + (let ((message with-editor-cancel-message)) + (when (functionp message) + (setq message (funcall message))) + (let ((with-editor-post-cancel-hook-1 + (ignore-errors (delq t with-editor-post-cancel-hook))) + (with-editor-cancel-alist nil)) + (run-hooks 'with-editor-pre-cancel-hook) + (with-editor-return t) + (accept-process-output nil 0.1) + (run-hooks 'with-editor-post-cancel-hook-1)) + (message (or message "Canceled by user"))))) + +(defun with-editor-return (cancel) + (let ((winconf with-editor-previous-winconf) + (clients server-buffer-clients) + (dir default-directory) + (pid with-editor--pid)) + (remove-hook 'kill-buffer-query-functions + 'with-editor-kill-buffer-noop t) + (cond (cancel + (save-buffer) + (if clients + (dolist (client clients) + (ignore-errors + (server-send-string client "-error Canceled by user")) + (delete-process client)) + ;; Fallback for when emacs was used as $EDITOR instead + ;; of emacsclient or the sleeping editor. See #2258. + (ignore-errors (delete-file buffer-file-name)) + (kill-buffer))) + (t + (save-buffer) + (if clients + ;; Don't use `server-edit' because we do not want to show + ;; another buffer belonging to another client. See #2197. + (server-done) + (kill-buffer)))) + (when pid + (let ((default-directory dir)) + (process-file "kill" nil nil nil + "-s" (if cancel "USR2" "USR1") pid))) + (when (and winconf (eq (window-configuration-frame winconf) + (selected-frame))) + (set-window-configuration winconf)))) + +;;; Mode + +(defvar with-editor-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "\C-c\C-c" 'with-editor-finish) + (define-key map [remap server-edit] 'with-editor-finish) + (define-key map "\C-c\C-k" 'with-editor-cancel) + (define-key map [remap kill-buffer] 'with-editor-cancel) + (define-key map [remap ido-kill-buffer] 'with-editor-cancel) + (define-key map [remap iswitchb-kill-buffer] 'with-editor-cancel) + map)) + +(define-minor-mode with-editor-mode + "Edit a file as the $EDITOR of an external process." + :lighter with-editor-mode-lighter + ;; Protect the user from killing the buffer without using + ;; either `with-editor-finish' or `with-editor-cancel', + ;; and from removing the key bindings for these commands. + (unless with-editor-mode + (error "With-Editor mode cannot be turned off")) + (add-hook 'kill-buffer-query-functions + 'with-editor-kill-buffer-noop nil t) + ;; `server-execute' displays a message which is not + ;; correct when using this mode. + (when with-editor-show-usage + (with-editor-usage-message))) + +(put 'with-editor-mode 'permanent-local t) + +(defun with-editor-kill-buffer-noop () + (message (substitute-command-keys "\ +Don't kill this buffer. Instead cancel using \\[with-editor-cancel]"))) + +(defun with-editor-usage-message () + ;; Run after `server-execute', which is run using + ;; a timer which starts immediately. + (run-with-timer + 0.01 nil `(lambda () + (with-current-buffer ,(current-buffer) + (message (substitute-command-keys "\ +Type \\[with-editor-finish] to finish, \ +or \\[with-editor-cancel] to cancel")))))) + +;;; Wrappers + +(defvar with-editor--envvar nil "For internal use.") + +(defmacro with-editor (&rest body) + "Use the Emacsclient as $EDITOR while evaluating BODY. +Modify the `process-environment' for processes started in BODY, +instructing them to use the Emacsclient as $EDITOR. If optional +ENVVAR is provided then bind that environment variable instead. +\n(fn [ENVVAR] BODY...)" + (declare (indent defun) (debug (body))) + `(let ((with-editor--envvar ,(if (stringp (car body)) + (pop body) + '(or with-editor--envvar "EDITOR"))) + (process-environment process-environment)) + (if (or (not with-editor-emacsclient-executable) + (file-remote-p default-directory)) + (setenv with-editor--envvar with-editor-sleeping-editor) + ;; Make sure server-use-tcp's value is valid. + (unless (featurep 'make-network-process '(:family local)) + (setq server-use-tcp t)) + ;; Make sure the server is running. + (unless server-process + (when (server-running-p server-name) + (setq server-name (format "server%s" (emacs-pid))) + (when (server-running-p server-name) + (server-force-delete server-name))) + (server-start)) + ;; Tell $EDITOR to use the Emacsclient. + (setenv with-editor--envvar + (concat (shell-quote-argument with-editor-emacsclient-executable) + ;; Tell the process where the server file is. + (and (not server-use-tcp) + (concat " --socket-name=" + (shell-quote-argument + (expand-file-name server-name + server-socket-dir)))))) + (when server-use-tcp + (setenv "EMACS_SERVER_FILE" + (expand-file-name server-name server-auth-dir))) + ;; As last resort fallback to the sleeping editor. + (setenv "ALTERNATE_EDITOR" with-editor-sleeping-editor)) + ,@body)) + +(defun with-editor-server-window () + (or (and buffer-file-name + (cdr (--first (string-match-p (car it) buffer-file-name) + with-editor-server-window-alist))) + server-window)) + +(defun server-switch-buffer--with-editor-server-window-alist + (fn &optional next-buffer killed-one filepos) + "Honor `with-editor-server-window-alist' (which see)." + (let ((server-window (with-current-buffer + (or next-buffer (current-buffer)) + (when with-editor-mode + (setq with-editor-previous-winconf + (current-window-configuration))) + (with-editor-server-window)))) + (funcall fn next-buffer killed-one filepos))) + +(advice-add 'server-switch-buffer :around + 'server-switch-buffer--with-editor-server-window-alist) + +(defun start-file-process--with-editor-process-filter + (fn name buffer program &rest program-args) + "When called inside a `with-editor' form and the Emacsclient +cannot be used, then give the process the filter function +`with-editor-process-filter'. To avoid overriding the filter +being added here you should use `with-editor-set-process-filter' +instead of `set-process-filter' inside `with-editor' forms. + +When the `default-directory' is located on a remote machine, +then also manipulate PROGRAM and PROGRAM-ARGS in order to set +the appropriate editor environment variable." + (if (not with-editor--envvar) + (apply fn name buffer program program-args) + (when (file-remote-p default-directory) + (unless (equal program "env") + (push program program-args) + (setq program "env")) + (push (concat with-editor--envvar "=" with-editor-sleeping-editor) + program-args)) + (let ((process (apply fn name buffer program program-args))) + (set-process-filter process 'with-editor-process-filter) + (process-put process 'default-dir default-directory) + process))) + +(advice-add 'start-file-process :around + 'start-file-process--with-editor-process-filter) + +(defun with-editor-set-process-filter (process filter) + "Like `set-process-filter' but keep `with-editor-process-filter'. +Give PROCESS the new FILTER but keep `with-editor-process-filter' +if that was added earlier by the adviced `start-file-process'. + +Do so by wrapping the two filter functions using a lambda, which +becomes the actual filter. It calls `with-editor-process-filter' +first, passing t as NO-STANDARD-FILTER. Then it calls FILTER, +which may or may not insert the text into the PROCESS' buffer." + (set-process-filter + process + (if (eq (process-filter process) 'with-editor-process-filter) + `(lambda (proc str) + (,filter proc str) + (with-editor-process-filter proc str t)) + filter))) + +(defvar with-editor-filter-visit-hook nil) + +(defun with-editor-output-filter (string) + (save-match-data + (if (string-match "^WITH-EDITOR: \\([0-9]+\\) OPEN \\(.+?\\)\r?$" string) + (let ((pid (match-string 1 string)) + (file (match-string 2 string))) + (with-current-buffer + (find-file-noselect + (if (file-name-absolute-p file) + (if (tramp-tramp-file-p default-directory) + (with-parsed-tramp-file-name default-directory nil + (tramp-make-tramp-file-name method user host file hop)) + file) + (expand-file-name file))) + (with-editor-mode 1) + (setq with-editor--pid pid) + (run-hooks 'with-editor-filter-visit-hook) + (funcall (or (with-editor-server-window) 'switch-to-buffer) + (current-buffer)) + (kill-local-variable 'server-window)) + nil) + string))) + +(defun with-editor-process-filter + (process string &optional no-default-filter) + "Listen for edit requests by child processes." + (let ((default-directory (process-get process 'default-dir))) + (with-editor-output-filter string)) + (unless no-default-filter + (internal-default-process-filter process string))) + +;;; Augmentations + +(cl-defun with-editor-export-editor (&optional (envvar "EDITOR")) + "Teach subsequent commands to use current Emacs instance as editor. + +Set and export the environment variable ENVVAR, by default +\"EDITOR\". The value is automatically generated to teach +commands use the current Emacs instance as \"the editor\". + +This works in `shell-mode', `term-mode' and `eshell-mode'." + (interactive (list (with-editor-read-envvar))) + (cond + ((derived-mode-p 'comint-mode 'term-mode) + (let* ((process (get-buffer-process (current-buffer))) + (filter (process-filter process))) + (set-process-filter process 'ignore) + (goto-char (process-mark process)) + (process-send-string + process (format "export %s=%s\n" envvar + (shell-quote-argument with-editor-sleeping-editor))) + (while (accept-process-output process 0.1)) + (set-process-filter process filter) + (if (derived-mode-p 'term-mode) + (with-editor-set-process-filter process 'with-editor-emulate-terminal) + (add-hook 'comint-output-filter-functions 'with-editor-output-filter + nil t)))) + ((derived-mode-p 'eshell-mode) + (add-to-list 'eshell-preoutput-filter-functions + 'with-editor-output-filter) + (setenv envvar with-editor-sleeping-editor)) + (t + (error "Cannot export environment variables in this buffer"))) + (message "Successfully exported %s" envvar)) + +(defun with-editor-export-git-editor () + "Like `with-editor-export-editor' but always set `$GIT_EDITOR'." + (interactive) + (with-editor-export-editor "GIT_EDITOR")) + +(defun with-editor-export-hg-editor () + "Like `with-editor-export-editor' but always set `$HG_EDITOR'." + (interactive) + (with-editor-export-editor "HG_EDITOR")) + +(defun with-editor-emulate-terminal (process string) + "Like `term-emulate-terminal' but also handle edit requests." + (when (with-editor-output-filter string) + (term-emulate-terminal process string))) + +(defvar with-editor-envvars '("EDITOR" "GIT_EDITOR" "HG_EDITOR")) + +(cl-defun with-editor-read-envvar + (&optional (prompt "Set environment variable") + (default "EDITOR")) + (let ((reply (completing-read (if default + (format "%s (%s): " prompt default) + (concat prompt ": ")) + with-editor-envvars nil nil nil nil default))) + (if (string= reply "") (user-error "Nothing selected") reply))) + +(define-minor-mode shell-command-with-editor-mode + "Teach `shell-command' to use current Emacs instance as editor. + +Teach `shell-command', and all commands that ultimately call that +command, to use the current Emacs instance as editor by executing +\"EDITOR=CLIENT COMMAND&\" instead of just \"COMMAND&\". + +CLIENT is automatically generated; EDITOR=CLIENT instructs +COMMAND to use to the current Emacs instance as \"the editor\", +assuming no other variable overrides the effect of \"$EDITOR\". +CLIENT may be the path to an appropriate emacsclient executable +with arguments, or a script which also works over Tramp. + +Alternatively you can use the `with-editor-async-shell-command', +which also allows the use of another variable instead of +\"EDITOR\"." + :global t) + +(defun with-editor-async-shell-command + (command &optional output-buffer error-buffer envvar) + "Like `async-shell-command' but with `$EDITOR' set. + +Execute string \"ENVVAR=CLIENT COMMAND\" in an inferior shell; +display output, if any. With a prefix argument prompt for an +environment variable, otherwise the default \"EDITOR\" variable +is used. With a negative prefix argument additionally insert +the COMMAND's output at point. + +CLIENT is automatically generated; ENVVAR=CLIENT instructs +COMMAND to use to the current Emacs instance as \"the editor\", +assuming it respects ENVVAR as an \"EDITOR\"-like variable. +CLIENT maybe the path to an appropriate emacsclient executable +with arguments, or a script which also works over Tramp. + +Also see `async-shell-command' and `shell-command'." + (interactive (with-editor-shell-command-read-args "Async shell command: " t)) + (let ((with-editor--envvar envvar)) + (with-editor + (async-shell-command command output-buffer error-buffer)))) + +(defun with-editor-shell-command + (command &optional output-buffer error-buffer envvar) + "Like `shell-command' or `with-editor-async-shell-command'. +If COMMAND ends with \"&\" behave like the latter, +else like the former." + (interactive (with-editor-shell-command-read-args "Shell command: ")) + (if (string-match "&[ \t]*\\'" command) + (with-editor-async-shell-command + command output-buffer error-buffer envvar) + (shell-command command output-buffer error-buffer))) + +(defun with-editor-shell-command-read-args (prompt &optional async) + (let ((command (read-shell-command + prompt nil nil + (--when-let (or buffer-file-name + (and (eq major-mode 'dired-mode) + (dired-get-filename nil t))) + (file-relative-name it))))) + (list command + (if (or async (setq async (string-match-p "&[ \t]*\\'" command))) + (< (prefix-numeric-value current-prefix-arg) 0) + current-prefix-arg) + shell-command-default-error-buffer + (and async current-prefix-arg (with-editor-read-envvar))))) + +(defun shell-command--shell-command-with-editor-mode + (fn command &optional output-buffer error-buffer) + (cond ((or (not (or with-editor--envvar shell-command-with-editor-mode)) + (not (string-match-p "&\\'" command))) + (funcall fn command output-buffer error-buffer)) + ((and with-editor-emacsclient-executable + (not (file-remote-p default-directory))) + (with-editor (funcall fn command output-buffer error-buffer))) + (t + (apply fn (format "%s=%s %s" + (or with-editor--envvar "EDITOR") + (shell-quote-argument with-editor-sleeping-editor) + command) + output-buffer error-buffer) + (ignore-errors + (let ((process (get-buffer-process + (or output-buffer + (get-buffer "*Async Shell Command*"))))) + (set-process-filter + process (lambda (proc str) + (comint-output-filter proc str) + (with-editor-process-filter proc str t))) + process))))) + +(advice-add 'shell-command :around + 'shell-command--shell-command-with-editor-mode) + +;;; with-editor.el ends soon + +(defun with-editor-debug () + "Debug configuration issues. +See `with-editor.info' for instructions." + (interactive) + (with-current-buffer (get-buffer-create "*with-editor-debug*") + (pop-to-buffer (current-buffer)) + (erase-buffer) + (ignore-errors (with-editor)) + (insert + (format "with-editor: %s\n" (locate-library "with-editor.el")) + (format "emacs: %s (%s)\n" + (expand-file-name invocation-name invocation-directory) + emacs-version) + "system:\n" + (format " system-type: %s\n" system-type) + (format " system-configuration: %s\n" system-configuration) + (format " system-configuration-options: %s\n" system-configuration-options) + "server:\n" + (format " server-running-p: %s\n" (server-running-p)) + (format " server-process: %S\n" server-process) + (format " server-use-tcp: %s\n" server-use-tcp) + (format " server-name: %s\n" server-name) + (format " server-socket-dir: %s\n" server-socket-dir)) + (if (and server-socket-dir (file-accessible-directory-p server-socket-dir)) + (--each (directory-files server-socket-dir nil "^[^.]") + (insert (format " %s\n" it))) + (insert (format " %s: not an accessible directory\n" + (if server-use-tcp "WARNING" "ERROR")))) + (insert (format " server-auth-dir: %s\n" server-auth-dir)) + (if (file-accessible-directory-p server-auth-dir) + (--each (directory-files server-auth-dir nil "^[^.]") + (insert (format " %s\n" it))) + (insert (format " %s: not an accessible directory\n" + (if server-use-tcp "ERROR" "WARNING")))) + (let ((val with-editor-emacsclient-executable) + (def (default-value 'with-editor-emacsclient-executable)) + (fun (let ((warning-minimum-level :error) + (warning-minimum-log-level :error)) + (with-editor-locate-emacsclient)))) + (insert "magit-emacsclient-executable:\n" + (format " value: %s (%s)\n" val + (and val (with-editor-emacsclient-version val))) + (format " default: %s (%s)\n" def + (and def (with-editor-emacsclient-version def))) + (format " funcall: %s (%s)\n" fun + (and fun (with-editor-emacsclient-version fun))))) + (insert "path:\n" + (format " $PATH: %S\n" (getenv "PATH")) + (format " exec-path: %s\n" exec-path)) + (insert (format " with-editor-emacsclient-path:\n")) + (--each (with-editor-emacsclient-path) + (insert (format " %s (%s)\n" it (car (file-attributes it)))) + (when (file-directory-p it) + (dolist (exec (directory-files it t "emacsclient")) + (insert (format " %s (%s)\n" exec + (with-editor-emacsclient-version exec)))))))) + +(defconst with-editor-font-lock-keywords + '(("(\\(with-\\(?:git-\\)?editor\\)\\_>" (1 'font-lock-keyword-face)))) +(font-lock-add-keywords 'emacs-lisp-mode with-editor-font-lock-keywords) + +(provide 'with-editor) +;; Local Variables: +;; indent-tabs-mode: nil +;; End: +;;; with-editor.el ends here diff --git a/with-editor.info b/with-editor.info new file mode 100644 index 0000000..41419b9 --- /dev/null +++ b/with-editor.info @@ -0,0 +1,323 @@ +This is with-editor.info, produced by makeinfo version 5.2 from +with-editor.texi. + +The library ‘with-editor’ makes it easy to use the Emacsclient as the +‘$EDITOR’ of child processes, making sure they know how to call home. +For remote processes a substitute is provided, which communicates with +Emacs on standard output instead of using a socket as the Emacsclient +does. + + This library was written because Magit has to be able to do the above +to allow the user to edit commit messages gracefully and to edit rebase +sequences, which wouldn’t be possible at all otherwise. + + Because other packages can benefit from such functionality, this +library is made available as a separate package. It also defines some +additional functionality which makes it useful even for end-users, who +don’t use Magit or another package which uses it internally. + + Copyright (C) 2015-2016 Jonas Bernoulli <jonas@bernoul.li> + + You can redistribute this document and/or modify it under the terms + of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) + any later version. + + This document 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. +INFO-DIR-SECTION Emacs +START-INFO-DIR-ENTRY +* With-Editor: (with-editor). Using the Emacsclient as $EDITOR. +END-INFO-DIR-ENTRY + + +File: with-editor.info, Node: Top, Next: Using the With-Editor package, Up: (dir) + +With-Editor User Manual +*********************** + +The library ‘with-editor’ makes it easy to use the Emacsclient as the +‘$EDITOR’ of child processes, making sure they know how to call home. +For remote processes a substitute is provided, which communicates with +Emacs on standard output instead of using a socket as the Emacsclient +does. + + This library was written because Magit has to be able to do the above +to allow the user to edit commit messages gracefully and to edit rebase +sequences, which wouldn’t be possible at all otherwise. + + Because other packages can benefit from such functionality, this +library is made available as a separate package. It also defines some +additional functionality which makes it useful even for end-users, who +don’t use Magit or another package which uses it internally. + + Copyright (C) 2015-2016 Jonas Bernoulli <jonas@bernoul.li> + + You can redistribute this document and/or modify it under the terms + of the GNU General Public License as published by the Free Software + Foundation, either version 3 of the License, or (at your option) + any later version. + + This document 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. + +* Menu: + +* Using the With-Editor package:: +* Using With-Editor as a library:: +* Debugging:: + +— The Detailed Node Listing — + +Using the With-Editor package + +* Configuring With-Editor:: +* Using With-Editor commands:: + + + +File: with-editor.info, Node: Using the With-Editor package, Next: Using With-Editor as a library, Prev: Top, Up: Top + +1 Using the With-Editor package +******************************* + +The ‘With-Editor’ package is used internally by Magit when editing +commit messages and rebase sequences. It also provides some commands +and features which are useful by themselves, even if you don’t use +Magit. + + For information about using this library in you own package, see +*note Using With-Editor as a library: Using With-Editor as a library. + +* Menu: + +* Configuring With-Editor:: +* Using With-Editor commands:: + + +File: with-editor.info, Node: Configuring With-Editor, Next: Using With-Editor commands, Up: Using the With-Editor package + +1.1 Configuring With-Editor +=========================== + +With-Editor tries very hard to locate a suitable emacsclient executable, +so ideally you should never have to customize the option +‘with-editor-emacsclient-executable’. When it fails to do so, then the +most likely reason is that someone found yet another way to package +Emacs (most likely on OS X) without putting the executable on ‘$PATH’, +and we have to add another kludge to find it anyway. + + -- User Option: with-editor-emacsclient-executable + + The emacsclient executable used as the editor by child process of + this Emacs instance. By using this executable, child processes can + call home to their parent process. + + This option is automatically set at startup by looking in + ‘exec-path’, and other places where the executable could be + installed, to find the emacsclient executable most suitable for the + current emacs instance. + + You should *not* customize this option permanently. If you have to + do it, then you should consider that a temporary kludge and inform + the Magit maintainer as described in *note Debugging: Debugging. + + If With-Editor fails to find a suitable emacsclient on you system, + then this should be fixed for all users at once, by teaching + ‘with-editor-locate-emacsclient’ how to so on your system and + system like yours. Doing it this way has the advantage, that you + won’t have do it again every time you update Emacs, and that other + users who have installed Emacs the same way as you have, won’t have + to go through the same trouble. + + Note that there also is a nuclear option; setting this variable to + ‘nil’ causes the "sleeping editor" described below to be used even + for local child processes. Obviously we don’t recommend that you + use this except in "emergencies", i.e. before we had a change to + add a kludge appropriate for you setup. + + -- Function: with-editor-locate-emacsclient + + The function used to set the initial value of the option + ‘with-editor-emacsclient-executable’. There’s a lot of voodoo + here. + + The emacsclient cannot be used when using Tramp to run a process on a +remote machine. (Theoretically it could, but that would be hard to +setup, very fragile, and rather insecure). + + With-Editor provides an alternative "editor" which can be used by +remote processes in much the same way as local processes use an +emacsclient executable. This alternative is known as the "sleeping +editor" because it is implemented as a shell script which sleeps until +it receives a signal. + + -- User Option: with-editor-sleeping-editor + + The sleeping editor is a shell script used as the editor of child + processes when the emacsclient executable cannot be used. + + This fallback is used for asynchronous process started inside the + macro ‘with-editor’, when the process runs on a remote machine or + for local processes when ‘with-editor-emacsclient-executable’ is + ‘nil’. + + Where the latter uses a socket to communicate with Emacs’ server, + this substitute prints edit requests to its standard output on + which a process filter listens for such requests. As such it is + not a complete substitute for a proper Emacsclient, it can only be + used as ‘$EDITOR’ of child process of the current Emacs instance. + + It is unlikely that you should ever have to customize this option. + + +File: with-editor.info, Node: Using With-Editor commands, Prev: Configuring With-Editor, Up: Using the With-Editor package + +1.2 Using With-Editor commands +============================== + +This section describes how to use the ‘with-editor’ library _outside_ of +Magit. You don’t need to know any of this just to create commits using +Magit. + + The commands ‘with-editor-async-shell-command’ and +‘with-editor-shell-command’ are intended as drop in replacements for +‘async-shell-command’ and ‘shell-command’. They automatically export +‘$EDITOR’ making sure the executed command uses the current Emacs +instance as "the editor". With a prefix argument these commands prompt +for an alternative environment variable such as ‘$GIT_EDITOR’. + + -- Command: with-editor-async-shell-command + + Like ‘async-shell-command’, but the command is run with the current + Emacs instance exported as ‘$EDITOR’. + + -- Command: with-editor-shell-command + + Like ‘async-shell-command’, but the command is run with the current + Emacs instance exported as ‘$EDITOR’. This only has an effect if + the command is run asynchronously, i.e. when the command ends with + ‘&’. + + To always use these variants add this to you init file: + + (define-key (current-global-map) + [remap async-shell-command] 'with-editor-async-shell-command) + (define-key (current-global-map) + [remap shell-command] 'with-editor-shell-command) + + Alternatively use the global ‘shell-command-with-editor-mode’. + + -- Variable: shell-command-with-editor-mode + + When this mode is active, then ‘$EDITOR’ is exported whenever + ultimately ‘shell-command’ is called to asynchronously run some + shell command. This affects most variants of that command, whether + they are defined in Emacs or in some third-party package. + + The command ‘with-editor-export-editor’ exports ‘$EDITOR’ or another +such environment variable in ‘shell-mode’, ‘term-mode’ and ‘eshell-mode’ +buffers. Use this Emacs command before executing a shell command which +needs the editor set, or always arrange for the current Emacs instance +to be used as editor by adding it to the appropriate mode hooks: + + (add-hook 'shell-mode-hook 'with-editor-export-editor) + (add-hook 'term-mode-hook 'with-editor-export-editor) + (add-hook 'eshell-mode-hook 'with-editor-export-editor) + + Some variants of this function exist; these two forms are equivalent: + + (add-hook 'shell-mode-hook + (apply-partially 'with-editor-export-editor "GIT_EDITOR")) + (add-hook 'shell-mode-hook 'with-editor-export-git-editor) + + -- Command: with-editor-export-editor + + When invoked in a ‘shell-mode’, ‘term-mode’, or ‘eshell-mode’ + buffer, this command teaches shell commands to use the current + Emacs instance as the editor, by exporting ‘$EDITOR’. + + -- Command: with-editor-export-git-editor + + Like ‘with-editor-export-editor’ but exports ‘$GIT_EDITOR’. + + -- Command: with-editor-export-hg-editor + + Like ‘with-editor-export-editor’ but exports ‘$HG_EDITOR’. + + +File: with-editor.info, Node: Using With-Editor as a library, Next: Debugging, Prev: Using the With-Editor package, Up: Top + +2 Using With-Editor as a library +******************************** + +This section describes how to use the with-editor library _outside_ of +Magit to teach another package how to have its child processes call +home, just like Magit does. You don’t need to know any of this just to +create commits using Magit. You can also ignore this if you use +‘with-editor’ outside of Magit, but only as an end-user. + + For information about interactive use and options which affect both +interactive and non-interactive use, see *note Using the With-Editor +package: Using the With-Editor package. + + -- Macro: with-editor &rest body + + This macro arranges for the emacsclient or the sleeping editor to + be used as the editor of child processes, effectively teaching them + to call home to the current emacs instance when they require that + the user edits a file. + + This is essentially done by establishing a local binding for + ‘process-environment’ and changing the value of the ‘$EDITOR’ + environment variable. This affects all processes started by forms + inside BODY. + + -- Function: with-editor-set-process-filter process filter + + This function is like ‘set-process-filter’ but ensures that adding + the new FILTER does not remove the ‘with-editor-process-filter’. + This is done by wrapping the two filter functions using a lambda, + which becomes the actual filter. It calls + ‘with-editor-process-filter’ first, passing ‘t’ as + NO-STANDARD-FILTER. Then it calls FILTER. + + +File: with-editor.info, Node: Debugging, Prev: Using With-Editor as a library, Up: Top + +3 Debugging +*********** + +With-Editor tries very hard to locate a suitable emacsclient executable, +and then sets option ‘with-editor-emacsclient-executable’ accordingly. +In very rare cases this fails. When it does fail, then the most likely +reason is that someone found yet another way to package Emacs (most +likely on OS X) without putting the executable on ‘$PATH’, and we have +to add another kludge to find it anyway. + + If you are having problems using ‘with-editor’, e.g. you cannot +commit in Magit, then please open a new issue at +<https://github.com/magit/magit/issues> and provide information about +your Emacs installation. Most importantly how did you install Emacs and +what is the output of ‘M-x with-editor-debug’? + + + +Tag Table: +Node: Top1545 +Node: Using the With-Editor package3237 +Node: Configuring With-Editor3853 +Node: Using With-Editor commands7460 +Node: Using With-Editor as a library10623 +Node: Debugging12295 + +End Tag Table + + +Local Variables: +coding: utf-8 +End: diff --git a/with-editor.org b/with-editor.org new file mode 100644 index 0000000..73c11f7 --- /dev/null +++ b/with-editor.org @@ -0,0 +1,258 @@ +#+TITLE: With-Editor User Manual +#+AUTHOR: Jonas Bernoulli +#+EMAIL: jonas@bernoul.li +#+DATE: 2015-2016 +#+LANGUAGE: en + +#+TEXINFO_DIR_CATEGORY: Emacs +#+TEXINFO_DIR_TITLE: With-Editor: (with-editor). +#+TEXINFO_DIR_DESC: Using the Emacsclient as $EDITOR +#+SUBTITLE: for version 2.5 + +#+OPTIONS: H:4 num:3 toc:2 + +* Copying +:PROPERTIES: +:COPYING: t +:END: + +#+BEGIN_TEXINFO +@ifnottex +The library @code{with-editor} makes it easy to use the Emacsclient as +the @code{$EDITOR} of child processes, making sure they know how to call +home. For remote processes a substitute is provided, which communicates +with Emacs on standard output instead of using a socket as the Emacsclient +does. + +This library was written because Magit has to be able to do the above +to allow the user to edit commit messages gracefully and to edit +rebase sequences, which wouldn't be possible at all otherwise. + +Because other packages can benefit from such functionality, this +library is made available as a separate package. It also defines some +additional functionality which makes it useful even for end-users, who +don't use Magit or another package which uses it internally. +@end ifnottex + +@quotation +Copyright (C) 2015-2016 Jonas Bernoulli <jonas@@bernoul.li> + +You can redistribute this document and/or modify it under the terms +of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any +later version. + +This document 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. +@end quotation +#+END_TEXINFO + +* Using the With-Editor package + +The ~With-Editor~ package is used internally by Magit when editing +commit messages and rebase sequences. It also provides some commands +and features which are useful by themselves, even if you don't use +Magit. + +For information about using this library in you own package, see +[[*Using With-Editor as a library]]. + +** Configuring With-Editor + +With-Editor tries very hard to locate a suitable emacsclient +executable, so ideally you should never have to customize the option +~with-editor-emacsclient-executable~. When it fails to do so, then the +most likely reason is that someone found yet another way to package +Emacs (most likely on OS X) without putting the executable on ~$PATH~, +and we have to add another kludge to find it anyway. + +- User Option: with-editor-emacsclient-executable + + The emacsclient executable used as the editor by child process of + this Emacs instance. By using this executable, child processes can + call home to their parent process. + + This option is automatically set at startup by looking in ~exec-path~, + and other places where the executable could be installed, to find + the emacsclient executable most suitable for the current emacs + instance. + + You should *not* customize this option permanently. If you have to do + it, then you should consider that a temporary kludge and inform the + Magit maintainer as described in [[*Debugging][Debugging]]. + + If With-Editor fails to find a suitable emacsclient on you system, + then this should be fixed for all users at once, by teaching + ~with-editor-locate-emacsclient~ how to so on your system and system + like yours. Doing it this way has the advantage, that you won't have + do it again every time you update Emacs, and that other users who + have installed Emacs the same way as you have, won't have to go + through the same trouble. + + Note that there also is a nuclear option; setting this variable to + ~nil~ causes the "sleeping editor" described below to be used even for + local child processes. Obviously we don't recommend that you use + this except in "emergencies", i.e. before we had a change to add a + kludge appropriate for you setup. + +- Function: with-editor-locate-emacsclient + + The function used to set the initial value of the option + ~with-editor-emacsclient-executable~. There's a lot of voodoo here. + +The emacsclient cannot be used when using Tramp to run a process on a +remote machine. (Theoretically it could, but that would be hard to +setup, very fragile, and rather insecure). + +With-Editor provides an alternative "editor" which can be used by +remote processes in much the same way as local processes use an +emacsclient executable. This alternative is known as the "sleeping +editor" because it is implemented as a shell script which sleeps until +it receives a signal. + +- User Option: with-editor-sleeping-editor + + The sleeping editor is a shell script used as the editor of child + processes when the emacsclient executable cannot be used. + + This fallback is used for asynchronous process started inside the + macro ~with-editor~, when the process runs on a remote machine or for + local processes when ~with-editor-emacsclient-executable~ is ~nil~. + + Where the latter uses a socket to communicate with Emacs' server, + this substitute prints edit requests to its standard output on + which a process filter listens for such requests. As such it is + not a complete substitute for a proper Emacsclient, it can only + be used as ~$EDITOR~ of child process of the current Emacs instance. + + It is unlikely that you should ever have to customize this option. + +** Using With-Editor commands + +This section describes how to use the ~with-editor~ library /outside/ of +Magit. You don't need to know any of this just to create commits +using Magit. + +The commands ~with-editor-async-shell-command~ and +~with-editor-shell-command~ are intended as drop in replacements for +~async-shell-command~ and ~shell-command~. They automatically export +~$EDITOR~ making sure the executed command uses the current Emacs +instance as "the editor". With a prefix argument these commands +prompt for an alternative environment variable such as ~$GIT_EDITOR~. + +- Command: with-editor-async-shell-command + + Like ~async-shell-command~, but the command is run with the current + Emacs instance exported as ~$EDITOR~. + +- Command: with-editor-shell-command + + Like ~async-shell-command~, but the command is run with the current + Emacs instance exported as ~$EDITOR~. This only has an effect if + the command is run asynchronously, i.e. when the command ends + with ~&~. + +To always use these variants add this to you init file: + +#+BEGIN_SRC emacs-lisp + (define-key (current-global-map) + [remap async-shell-command] 'with-editor-async-shell-command) + (define-key (current-global-map) + [remap shell-command] 'with-editor-shell-command) +#+END_SRC + +Alternatively use the global ~shell-command-with-editor-mode~. + +- Variable: shell-command-with-editor-mode + + When this mode is active, then ~$EDITOR~ is exported whenever + ultimately ~shell-command~ is called to asynchronously run some shell + command. This affects most variants of that command, whether they + are defined in Emacs or in some third-party package. + +The command ~with-editor-export-editor~ exports ~$EDITOR~ or +another such environment variable in ~shell-mode~, ~term-mode~ and +~eshell-mode~ buffers. Use this Emacs command before executing a +shell command which needs the editor set, or always arrange for the +current Emacs instance to be used as editor by adding it to the +appropriate mode hooks: + +#+BEGIN_SRC emacs-lisp + (add-hook 'shell-mode-hook 'with-editor-export-editor) + (add-hook 'term-mode-hook 'with-editor-export-editor) + (add-hook 'eshell-mode-hook 'with-editor-export-editor) +#+END_SRC + +Some variants of this function exist; these two forms are equivalent: + +#+BEGIN_SRC emacs-lisp + (add-hook 'shell-mode-hook + (apply-partially 'with-editor-export-editor "GIT_EDITOR")) + (add-hook 'shell-mode-hook 'with-editor-export-git-editor) +#+END_SRC + +- Command: with-editor-export-editor + + When invoked in a ~shell-mode~, ~term-mode~, or ~eshell-mode~ buffer, this + command teaches shell commands to use the current Emacs instance as + the editor, by exporting ~$EDITOR~. + +- Command: with-editor-export-git-editor + + Like ~with-editor-export-editor~ but exports ~$GIT_EDITOR~. + +- Command: with-editor-export-hg-editor + + Like ~with-editor-export-editor~ but exports ~$HG_EDITOR~. + +* Using With-Editor as a library + +This section describes how to use the =with-editor= library /outside/ of +Magit to teach another package how to have its child processes call +home, just like Magit does. You don't need to know any of this just +to create commits using Magit. You can also ignore this if you use +~with-editor~ outside of Magit, but only as an end-user. + +For information about interactive use and options which affect both +interactive and non-interactive use, see [[*Using the With-Editor +package]]. + +- Macro: with-editor &rest body + + This macro arranges for the emacsclient or the sleeping editor to be + used as the editor of child processes, effectively teaching them to + call home to the current emacs instance when they require that the + user edits a file. + + This is essentially done by establishing a local binding for + ~process-environment~ and changing the value of the ~$EDITOR~ + environment variable. This affects all processes started by forms + inside BODY. + +- Function: with-editor-set-process-filter process filter + + This function is like ~set-process-filter~ but ensures that adding the + new FILTER does not remove the ~with-editor-process-filter~. This is + done by wrapping the two filter functions using a lambda, which + becomes the actual filter. It calls ~with-editor-process-filter~ + first, passing ~t~ as NO-STANDARD-FILTER. Then it calls FILTER. + +* Debugging + +With-Editor tries very hard to locate a suitable emacsclient +executable, and then sets option ~with-editor-emacsclient-executable~ +accordingly. In very rare cases this fails. When it does fail, then +the most likely reason is that someone found yet another way to +package Emacs (most likely on OS X) without putting the executable on +~$PATH~, and we have to add another kludge to find it anyway. + +If you are having problems using ~with-editor~, e.g. you cannot commit +in Magit, then please open a new issue at +https://github.com/magit/magit/issues and provide information about +your Emacs installation. Most importantly how did you install Emacs +and what is the output of ~M-x with-editor-debug~? + +# LocalWords: Emacsclient LocalWords Magit async emacs emacsclient +# LocalWords: hg init rebase startup diff --git a/with-editor.texi b/with-editor.texi new file mode 100644 index 0000000..3b34b73 --- /dev/null +++ b/with-editor.texi @@ -0,0 +1,314 @@ +\input texinfo @c -*- texinfo -*- +@c %**start of header +@setfilename ./with-editor.info +@settitle With-Editor User Manual +@documentencoding UTF-8 +@documentlanguage en +@c %**end of header + +@copying +@ifnottex +The library @code{with-editor} makes it easy to use the Emacsclient as +the @code{$EDITOR} of child processes, making sure they know how to call +home. For remote processes a substitute is provided, which communicates +with Emacs on standard output instead of using a socket as the Emacsclient +does. + +This library was written because Magit has to be able to do the above +to allow the user to edit commit messages gracefully and to edit +rebase sequences, which wouldn't be possible at all otherwise. + +Because other packages can benefit from such functionality, this +library is made available as a separate package. It also defines some +additional functionality which makes it useful even for end-users, who +don't use Magit or another package which uses it internally. +@end ifnottex + +@quotation +Copyright (C) 2015-2016 Jonas Bernoulli <jonas@@bernoul.li> + +You can redistribute this document and/or modify it under the terms +of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any +later version. + +This document 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. +@end quotation +@end copying + +@dircategory Emacs +@direntry +* With-Editor: (with-editor). Using the Emacsclient as $EDITOR. +@end direntry + +@finalout +@titlepage +@title With-Editor User Manual +@subtitle for version 2.5 +@author Jonas Bernoulli +@page +@vskip 0pt plus 1filll +@insertcopying +@end titlepage + +@contents + +@ifnottex +@node Top +@top With-Editor User Manual +@insertcopying +@end ifnottex + + +@menu +* Using the With-Editor package:: +* Using With-Editor as a library:: +* Debugging:: + +@detailmenu +--- The Detailed Node Listing --- + +Using the With-Editor package + +* Configuring With-Editor:: +* Using With-Editor commands:: + +@end detailmenu +@end menu + + + +@node Using the With-Editor package +@chapter Using the With-Editor package + +The @code{With-Editor} package is used internally by Magit when editing +commit messages and rebase sequences. It also provides some commands +and features which are useful by themselves, even if you don't use +Magit. + +For information about using this library in you own package, see +@ref{Using With-Editor as a library,Using With-Editor as a library}. + +@menu +* Configuring With-Editor:: +* Using With-Editor commands:: +@end menu + +@node Configuring With-Editor +@section Configuring With-Editor + +With-Editor tries very hard to locate a suitable emacsclient +executable, so ideally you should never have to customize the option +@code{with-editor-emacsclient-executable}. When it fails to do so, then the +most likely reason is that someone found yet another way to package +Emacs (most likely on OS X) without putting the executable on @code{$PATH}, +and we have to add another kludge to find it anyway. + +@defopt with-editor-emacsclient-executable + +The emacsclient executable used as the editor by child process of +this Emacs instance. By using this executable, child processes can +call home to their parent process. + +This option is automatically set at startup by looking in @code{exec-path}, +and other places where the executable could be installed, to find +the emacsclient executable most suitable for the current emacs +instance. + +You should @strong{not} customize this option permanently. If you have to do +it, then you should consider that a temporary kludge and inform the +Magit maintainer as described in @ref{Debugging,Debugging}. + +If With-Editor fails to find a suitable emacsclient on you system, +then this should be fixed for all users at once, by teaching +@code{with-editor-locate-emacsclient} how to so on your system and system +like yours. Doing it this way has the advantage, that you won't have +do it again every time you update Emacs, and that other users who +have installed Emacs the same way as you have, won't have to go +through the same trouble. + +Note that there also is a nuclear option; setting this variable to +@code{nil} causes the "sleeping editor" described below to be used even for +local child processes. Obviously we don't recommend that you use +this except in "emergencies", i.e. before we had a change to add a +kludge appropriate for you setup. +@end defopt + +@defun with-editor-locate-emacsclient + +The function used to set the initial value of the option +@code{with-editor-emacsclient-executable}. There's a lot of voodoo here. +@end defun + +The emacsclient cannot be used when using Tramp to run a process on a +remote machine. (Theoretically it could, but that would be hard to +setup, very fragile, and rather insecure). + +With-Editor provides an alternative "editor" which can be used by +remote processes in much the same way as local processes use an +emacsclient executable. This alternative is known as the "sleeping +editor" because it is implemented as a shell script which sleeps until +it receives a signal. + +@defopt with-editor-sleeping-editor + +The sleeping editor is a shell script used as the editor of child +processes when the emacsclient executable cannot be used. + +This fallback is used for asynchronous process started inside the +macro @code{with-editor}, when the process runs on a remote machine or for +local processes when @code{with-editor-emacsclient-executable} is @code{nil}. + +Where the latter uses a socket to communicate with Emacs' server, +this substitute prints edit requests to its standard output on +which a process filter listens for such requests. As such it is +not a complete substitute for a proper Emacsclient, it can only +be used as @code{$EDITOR} of child process of the current Emacs instance. + +It is unlikely that you should ever have to customize this option. +@end defopt + +@node Using With-Editor commands +@section Using With-Editor commands + +This section describes how to use the @code{with-editor} library @emph{outside} of +Magit. You don't need to know any of this just to create commits +using Magit. + +The commands @code{with-editor-async-shell-command} and +@code{with-editor-shell-command} are intended as drop in replacements for +@code{async-shell-command} and @code{shell-command}. They automatically export +@code{$EDITOR} making sure the executed command uses the current Emacs +instance as "the editor". With a prefix argument these commands +prompt for an alternative environment variable such as @code{$GIT_EDITOR}. + +@cindex with-editor-async-shell-command +@deffn Command with-editor-async-shell-command + +Like @code{async-shell-command}, but the command is run with the current +Emacs instance exported as @code{$EDITOR}. +@end deffn + +@cindex with-editor-shell-command +@deffn Command with-editor-shell-command + +Like @code{async-shell-command}, but the command is run with the current +Emacs instance exported as @code{$EDITOR}. This only has an effect if +the command is run asynchronously, i.e. when the command ends +with @code{&}. +@end deffn + +To always use these variants add this to you init file: + +@lisp +(define-key (current-global-map) + [remap async-shell-command] 'with-editor-async-shell-command) +(define-key (current-global-map) + [remap shell-command] 'with-editor-shell-command) +@end lisp + +Alternatively use the global @code{shell-command-with-editor-mode}. + +@defvar shell-command-with-editor-mode + +When this mode is active, then @code{$EDITOR} is exported whenever +ultimately @code{shell-command} is called to asynchronously run some shell +command. This affects most variants of that command, whether they +are defined in Emacs or in some third-party package. +@end defvar + +The command @code{with-editor-export-editor} exports @code{$EDITOR} or +another such environment variable in @code{shell-mode}, @code{term-mode} and +@code{eshell-mode} buffers. Use this Emacs command before executing a +shell command which needs the editor set, or always arrange for the +current Emacs instance to be used as editor by adding it to the +appropriate mode hooks: + +@lisp +(add-hook 'shell-mode-hook 'with-editor-export-editor) +(add-hook 'term-mode-hook 'with-editor-export-editor) +(add-hook 'eshell-mode-hook 'with-editor-export-editor) +@end lisp + +Some variants of this function exist; these two forms are equivalent: + +@lisp +(add-hook 'shell-mode-hook + (apply-partially 'with-editor-export-editor "GIT_EDITOR")) +(add-hook 'shell-mode-hook 'with-editor-export-git-editor) +@end lisp + +@cindex with-editor-export-editor +@deffn Command with-editor-export-editor + +When invoked in a @code{shell-mode}, @code{term-mode}, or @code{eshell-mode} buffer, this +command teaches shell commands to use the current Emacs instance as +the editor, by exporting @code{$EDITOR}. +@end deffn + +@cindex with-editor-export-git-editor +@deffn Command with-editor-export-git-editor + +Like @code{with-editor-export-editor} but exports @code{$GIT_EDITOR}. +@end deffn + +@cindex with-editor-export-hg-editor +@deffn Command with-editor-export-hg-editor + +Like @code{with-editor-export-editor} but exports @code{$HG_EDITOR}. +@end deffn + +@node Using With-Editor as a library +@chapter Using With-Editor as a library + +This section describes how to use the @verb{~with-editor~} library @emph{outside} of +Magit to teach another package how to have its child processes call +home, just like Magit does. You don't need to know any of this just +to create commits using Magit. You can also ignore this if you use +@code{with-editor} outside of Magit, but only as an end-user. + +For information about interactive use and options which affect both +interactive and non-interactive use, see @ref{Using the With-Editor package,Using the With-Editor package}. + +@defmac with-editor &rest body + +This macro arranges for the emacsclient or the sleeping editor to be +used as the editor of child processes, effectively teaching them to +call home to the current emacs instance when they require that the +user edits a file. + +This is essentially done by establishing a local binding for +@code{process-environment} and changing the value of the @code{$EDITOR} +environment variable. This affects all processes started by forms +inside BODY. +@end defmac + +@defun with-editor-set-process-filter process filter + +This function is like @code{set-process-filter} but ensures that adding the +new FILTER does not remove the @code{with-editor-process-filter}. This is +done by wrapping the two filter functions using a lambda, which +becomes the actual filter. It calls @code{with-editor-process-filter} +first, passing @code{t} as NO-STANDARD-FILTER. Then it calls FILTER. +@end defun + +@node Debugging +@chapter Debugging + +With-Editor tries very hard to locate a suitable emacsclient +executable, and then sets option @code{with-editor-emacsclient-executable} +accordingly. In very rare cases this fails. When it does fail, then +the most likely reason is that someone found yet another way to +package Emacs (most likely on OS X) without putting the executable on +@code{$PATH}, and we have to add another kludge to find it anyway. + +If you are having problems using @code{with-editor}, e.g. you cannot commit +in Magit, then please open a new issue at +@uref{https://github.com/magit/magit/issues} and provide information about +your Emacs installation. Most importantly how did you install Emacs +and what is the output of @code{M-x with-editor-debug}? + +@bye |
