How to split windows horizontally in emacs when executing current buffer? - emacs

I am coding in python using Emacs. I have modified ".emacs" to suit my needs. However, I do not know how to change the default vertical split behavior of windows when executing current buffer.
I know that:
(setq split-height-threshold nil)
(setq split-width-threshold 0)
works in general. But it does not work when I execute buffer for the first time using C-c C-c
Despite having the above code in my ~/.emacs, the following happens on C-c C-c :
I hope my question is clear, let me know if you have any idea about this.

I use the following. It fixes your problem (I hope) as well as preventing Emacs from splitting windows in the first place.
;;; ------------------------------------------------------------------
;;; display-buffer
;; The default behaviour of `display-buffer' is to always create a new
;; window. As I normally use a large display sporting a number of
;; side-by-side windows, this is a bit obnoxious.
;;
;; The code below will make Emacs reuse existing windows, with the
;; exception that if have a single window open in a large display, it
;; will be split horisontally.
(setq pop-up-windows nil)
(defun my-display-buffer-function (buf not-this-window)
(if (and (not pop-up-frames)
(one-window-p)
(or not-this-window
(not (eq (window-buffer (selected-window)) buf)))
(> (frame-width) 162))
(split-window-horizontally))
;; Note: Some modules sets `pop-up-windows' to t before calling
;; `display-buffer' -- Why, oh, why!
(let ((display-buffer-function nil)
(pop-up-windows nil))
(display-buffer buf not-this-window)))
(setq display-buffer-function 'my-display-buffer-function)

Related

emacs terminal mode: how to copy and paste efficiently

I'm having a hard time making this emacs -nw work effectively under the terminal mode (emacs -nw).
Some setup information:
The working server is connected via SSH, and emacs is running on the server. Usually I'm connecting using SSH and "emacs -nw" to work on my files.
The emacs config is picked up from: https://hugoheden.wordpress.com/2009/03/08/copypaste-with-emacs-in-terminal/
;; make mouse selection to be emacs region marking
(require 'mouse)
(xterm-mouse-mode t)
(defun track-mouse (e))
(setq mouse-sel-mode t)
;; enable clipboard in emacs
(setq x-select-enable-clipboard t)
;; enable copy/paste between emacs and other apps (terminal version of emacs)
(unless window-system
(when (getenv "DISPLAY")
;; Callback for when user cuts
(defun xsel-cut-function (text &optional push)
;; Insert text to temp-buffer, and "send" content to xsel stdin
(with-temp-buffer
(insert text)
;; I prefer using the "clipboard" selection (the one the
;; typically is used by c-c/c-v) before the primary selection
;; (that uses mouse-select/middle-button-click)
(call-process-region (point-min) (point-max) "xsel" nil 0 nil "--clipboard" "--input")))
;; Call back for when user pastes
(defun xsel-paste-function()
;; Find out what is current selection by xsel. If it is different
;; from the top of the kill-ring (car kill-ring), then return
;; it. Else, nil is returned, so whatever is in the top of the
;; kill-ring will be used.
(let ((xsel-output (shell-command-to-string "xsel --clipboard --output")))
(unless (string= (car kill-ring) xsel-output)
xsel-output )))
;; Attach callbacks to hooks
(setq interprogram-cut-function 'xsel-cut-function)
(setq interprogram-paste-function 'xsel-paste-function)
;; Idea from
;; http://shreevatsa.wordpress.com/2006/10/22/emacs-copypaste-and-x/
;; http://www.mail-archive.com/help-gnu-emacs#gnu.org/msg03577.html
))
The reason to have:
(require 'mouse)
(xterm-mouse-mode t)
(defun track-mouse (e))
(setq mouse-sel-mode t)
is to enable mouse selection over text such that the text region is highlighted just as "C-x SPC" marking the region. Then I can use "M-x w" to copy and "C-x y" to paste text within emacs and between emacs and other apps.
All look perfect except that any operations related to X are REALLY SLOW! My connection to the remote server is smooth -- the latency is usually under 100ms. But to kill one line of text using "C-x k", it takes ~5 seconds! To paste it, it takes another 5 seconds!
When copy/paste is frequent sometimes, this becomes really annoying. I think this is related to the X sever messaging, but not sure if there is good way to fix this.
Any ideas?
Thanks!
This is not an ideal solution per se, but i figured out a way that I feel better than the previous one.
The idea is to get rid of X which causes heavy latency issues, i.e. keep only the following:
;; enable clipboard in emacs
(setq x-select-enable-clipboard t)
The results are:
copy/paste within Emacs is straightforward and fast.
copy from other apps to Emacs: Ctrl+Shift+v
copy from Emacs to other apps: mouse selection is now on X Selection, so right-click and copy shall copy the text into the Selection. Note that 'M-w" now won't copy anything into Selection or system clipboard.
This is again a compromise rather than a solution, but considering the fact that i copy/paste more often than inter-app operations, this is acceptable at the moment.
Still looking forward to a good solution!
You can accomplish this by using a terminal escape code!
There is a unique category of terminal escape codes called "Operating System Controls" (OSC) and one of these sequences (\033]52) is meant for interacting with the system clipboard. The great thing is that your terminal doesn't care where the code came from so it will work in remote sessions as well.
Most terminal emulators support it (iTerm2, OS X Terminal, and I think all Linux terminals besides GNOME). You can test if your terminal supports this sequence by simply running:
$ printf "\033]52;c;$(printf "Hello, world" | base64)\a"
Then paste from your system clipboard. If it pastes "Hello, world" then your terminal supports it!
I have this function in my init.el so when I call yank-to-clipboard Emacs will yank the value from my kill ring into the system clipboard:
(defun yank-to-clipboard ()
"Use ANSI OSC 52 escape sequence to attempt clipboard copy"
(interactive)
(send-string-to-terminal
(format "\033]52;c;%s\a"
(base64-encode-string
(encode-coding-string
(substring-no-properties
(nth 0 kill-ring)) 'utf-8) t))))
As I type this, I stumbled upon an almost-identical script supported by Chromium community: https://chromium.googlesource.com/apps/libapps/+/master/hterm/etc/osc52.el
For those running Emacs inside Tmux:
Tmux consumes the sequence, so you'll need to pipe the sequence to the Tmux active tty for this to work. I have a solution in my blog post here: https://justinchips.medium.com/have-vim-emacs-tmux-use-system-clipboard-4c9d901eef40
To extend on #justinokamoto's answer for use in tmux, it works great and is truly amazing. I haven't debugged it with e.g. tramp or other fancy emacs settings but to get it to work
Follow https://sunaku.github.io/tmux-yank-osc52.html great instructions, modifying your tmux.conf and ~/bin/yank
Make sure terminal access to your clipboard is enabled on your terminal
Then to pull into emacs you can use a function like:
(Caveat emptor, I am very new to elisp. This writes to a temporary file in /tmp/yank)
(defun custom-terminal-yank (&rest args)
(message "-> CLIP")
;; FOR EVIL MODE: UNCOMMENT SO FIRST YANKS TO KILL RING
;; need to yank first, with all those args
;; ;; https://emacs.stackexchange.com/questions/19215/how-to-write-a-transparent-pass-through-function-wrapper
;; (interactive (advice-eval-interactive-spec
;; (cadr (interactive-form #'evil-yank))))
;; (apply #'evil-yank args)
;; https://stackoverflow.com/questions/27764059/emacs-terminal-mode-how-to-copy-and-paste-efficiently
;; https://sunaku.github.io/tmux-yank-osc52.html
(f-write-text (nth 0 kill-ring) 'utf-8 "/tmp/yank")
(send-string-to-terminal (shell-command-to-string "~/bin/yank /tmp/yank"))
)
If anyone else uses evil mode as well (just to make things complicated) you can uncomment those lines and use something like
(define-key evil-visual-state-map "Y" 'jonah-terminal-yank)
So that normal "y" is for normal yanking in visual mode, but "Y" is for cross-clipboard yanking

Execute a particular command on multiple emacs buffers

Is there a way to execute emacs command on multiple buffers without having to selecting them individually and executing it on each individual buffer.
I usually open multiple files matching a particular regex, e.g. ~/*.py and wish to enable a particular mode, say hs-minor-mode or glasses-mode on each, or say execute C-c # C-M-h on each. Currently I have to select each one of them and do it individually. So is there a hack or a loop to automate the task.
Lets say I mark the buffers from the buffer-list and then run the command for all those marked.
I tried this but after executing the commands in eval-expression I completely lost access to my minibuffer, meaning whenever I typed M-x the minibuffer returned this
unable to access the minibuffer emacs error "Process Menu Mode doesn't support Hideshow Minor Mode"
and I was forced to actually kill the entire emacs process because the C-x C-s wasn't working neither was the End Task.
PS: I have no experience in elisp
You can use ibuffer mode for this (It is part of the default Emacs distribution).
(global-set-key "\C-x\C-b" 'ibuffer) ;; make ibuffer the default
In *Ibuffer* you can mark the required buffers with m and then
execute a form in each with E.
Generally, ibuffer is a lot more flexible then the usual buffer list and I think ibuffer should really be the default buffer-list in Emacs.
If you do this often, you might want to switch those particular modes on every time you enter python mode by attaching them to the mode-hook:
(add-hook 'python-mode-hook 'hs-minor-mode)
(add-hook 'python-mode-hook 'glasses-mode)
I didn't know ibuffer had that feature!
Anyway, for those who are more familiar with dired, here is a command that do the same. Select the files in dired with m or any other more powerful method. Then do, M-xdired-do-command and write a form or a command just as in M-x.
(defun dired-do-command (command)
"Run COMMAND on marked files. Any files not already open will be opened.
After this command has been run, any buffers it's modified will remain
open and unsaved."
(interactive
(list
(let ((print-level nil)
(minibuffer-history-position 0)
(minibuffer-history-sexp-flag (1+ (minibuffer-depth))))
(unwind-protect
(read-from-minibuffer
"Command: " (prin1-to-string (nth 0 command-history))
read-expression-map t
(cons 'command-history 0))
;; If command was added to command-history as a
;; string, get rid of that. We want only
;; evaluable expressions there.
(if (stringp (car command-history))
(setq command-history (cdr command-history)))))))
(dolist (filename (dired-get-marked-files))
(with-current-buffer (find-file-noselect filename)
(if (symbolp command)
(call-interactively command)
(eval command)))))

Rearranging Windows Within Emacs?

Emacs tends to open two horizontally separated windows, one on top of the other (I think windows is the proper emacs term). Since I am working with a wide screen I find it easier and better to work with two vertically separated windows, arranged side by side within the emacs frame.
I know how to open a new vertically separated window using C-x 3 but how do you rearrange windows that emacs opens itself (for example when M-x compile is invoked opening a compilation/debugging window) from horizontal to vertical?
I had the same problem, this is what I currently use. Just drop it into your Emacs init file:
;; The default behaviour of `display-buffer' is to always create a new
;; window. As I normally use a large display sporting a number of
;; side-by-side windows, this is a bit obnoxious.
;;
;; The code below will make Emacs reuse existing windows, with the
;; exception that if have a single window open in a large display, it
;; will be split horisontally.
(setq pop-up-windows nil)
(defun my-display-buffer-function (buf not-this-window)
(if (and (not pop-up-frames)
(one-window-p)
(or not-this-window
(not (eq (window-buffer (selected-window)) buf)))
(> (frame-width) 162))
(split-window-horizontally))
;; Note: Some modules sets `pop-up-windows' to t before calling
;; `display-buffer' -- Why, oh, why!
(let ((display-buffer-function nil)
(pop-up-windows nil))
(display-buffer buf not-this-window)))
(setq display-buffer-function 'my-display-buffer-function)
Take a look at the split-height-threshold and the split-height-threshold variables, both customizable.
For further information on what values they take, C-h f split-window-sensibly RET. This Emacs Lisp touches the topic superficially.
This affects how display-buffer works, which probably compile and many other commands use.
Here's my solution:
(defun split-window-prefer-side-by-side (&optional window)
(let ((split-height-threshold (and (< (window-width window)
split-width-threshold)
split-height-threshold)))
(split-window-sensibly window)))
(setq split-window-preferred-function 'split-window-prefer-side-by-side)
This still consults split-*-threshold variable values, just prefers side-by-side windows when both splitting directions are acceptable.

How to open multiple terminals?

In Emacs, I often find myself in a situation where I need to jump back and forth between various source files to various terminals. However, I feel like I do not have a good way to do this efficiently and it's clumsy that you can only open one shell in Emacs (shell, eshell, or term).
Moreover, I need an efficient way of juggle between multiple terminals and source files.
How can I achieve this?
You can have as many terminals and shells open at once as you want. Just use M-x rename-buffer to change the name of an existing *term* or *shell* buffer, and the next time you do M-x term or M-x shell, a brand new buffer will be created. In the case of M-x shell, a prefix argument will cause you to be prompted for the name of the new shell buffer, as offby1 noted.
A few years ago I had a job where I had to regularly log in to various production servers named "host01.foo.com", "host02.foo.com", etc. I wrote a little function like this one to make it easier to manage them all:
(defun ssh-to-host (num)
(interactive "P")
(let* ((buffer-name (format "*host%02d*" num))
(buffer (get-buffer buffer-name)))
(if buffer
(switch-to-buffer buffer)
(term "/bin/bash")
(term-send-string
(get-buffer-process (rename-buffer buffer-name))
(format "ssh host%02d.foo.com\r" num)))))
Then I bound this command to (say) s-h (super H), enabling me to just type M-5 s-h. If I didn't already have a buffer named *host05*, it would start a new terminal emulator buffer, rename it to *host05*, and ssh me into host05.foo.com. If buffer *host05* already existed, it would simply switch me to it. Quite handy!
You can certainly have multiple interactive shells open. Try typing C-u M-x shell RET RET.
Try using MultiTerm to open multiple shells.
You can use Emacs Lisp Screen, which emulates GNU Screen and provides easy key bindings to jump to and between a number of different shells.
I use many methods for incorporating my terminal life into Emacs:
elscreen.el is a life saver, if you have a complicated window layout like gdb or have simply become overwhelmed with clutter you just open a new screen. In your case you could dedicate one screen to terminals.
multi-term.el makes managing terminals a bit easier.
shell-pop.el, a great tool for quick terminal access. shell-pop lets you assign a key to opening and closing a specific shell buffer window, if you've used drop-down terminals like tilda you know how incredibly handy this can be:
Here's and example of my shell-pop configuration, I use the key C-t to pop up an eshell:
(require 'shell-pop)
(shell-pop-set-internal-mode "eshell") ; Or "ansi-term" if you prefer
(shell-pop-set-window-height 60) ; Give shell buffer 60% of window
;; If you use "ansi-term" and want to use C-t
;; (defvar ansi-term-after-hook nil)
;; (add-hook 'ansi-term-after-hook
;; '(lambda ()
;; (define-key term-raw-map (kbd "C-t") 'shell-pop)))
;; (defadvice ansi-term (after ansi-term-after-advice (org))
;; (run-hooks 'ansi-term-after-hook))
;; (ad-activate 'ansi-term)
(global-set-key (kbd "C-t") 'shell-pop)
I usually do an an M-x server-start and then use emacsclient --no-wait to open files. I've aliased that to e with some embellishments so that it's a little more convenient.
I do all my work in a single terminal and just "throw" the files I want to edit into Emacs using e. Inside Emacs, I juggle around using iswitchb and it works just fine. YMMV.
I regularly used 10 or so shells in my old workplace. The secret is you have to rename additional shell buffers. I did this automatically though in my .emacs, creating and naming the shells logically (I had projnameRun and projnameBuild for every project). Worked really well together with anything, making it very easy to refind the right shell (you use the end of the project name combined with either r or b for run/build).
Instead of having several terminal windows in emacs, I spawn a different xterm whenever I need a new terminal. This of course is bearable because I use a very lightweight terminal emulator (urxvt) which starts in under 0.2s.
Then I use my window manager to switch between them and emacs frames. A configurable window manager will have plenty of options to tune to switch between windows (extremely) efficiently. Inside emacs, I use windmove and ido-mode, and have bound to C-tab a function that switches to the last buffer (because I use C-x b in that fashion a lot).
So um, not sure how useful it is to you since it's quite different from your use pattern, but this is what works for me.
I had exactly the same problem some years ago, and found nothing that satisfied me; so I wrote my own "toggle shell" function. It toggles between the current frame or window configuration and a system shell buffer. It can also put the shell into a dedicated frame, and inject a pushd to the current buffer directory.
This is an excerpt from my .emacs:
(defvar --toggle-shell-last-window-conf nil "The last window configuration.")
(defvar --toggle-shell-last-buf nil "The last buffer object in case there's no last window configuration.")
(defvar --toggle-shell-last-frame nil "The frame that was selected when opening a shell buffer.")
(defun --toggle-shell-have-conf ()
(window-configuration-p --toggle-shell-last-window-conf))
(defun --toggle-shell-store-last-conf ()
(setq --toggle-shell-last-buf (current-buffer)
--toggle-shell-last-frame (selected-frame)
--toggle-shell-last-window-conf (current-window-configuration)))
(defun --toggle-shell-restore-last-conf ()
(if (--toggle-shell-have-conf)
(progn (raise-frame --toggle-shell-last-frame)
(set-window-configuration --toggle-shell-last-window-conf))
(let ((bufnam (if (bufferp --toggle-shell-last-buf)
(buffer-name --toggle-shell-last-buf) --toggle-shell-last-buf)))
(if bufnam
(if (get-buffer bufnam) (switch-to-buffer bufnam t)
(message "%s: buffer not available" bufnam))))))
(defun --toggle-shell (&optional display inject-cd)
"Toggles between current buffers and a system shell buffer. With prefix-arg
close the shell.
When DISPLAY is 'vertical splits the shell as vertical window; when 'frame uses
a dedicated frame (default: single window). When INJECT-CD executes a `pushd'
to the working directory of the buffer from which you toggled the shell."
(interactive)
(let* ((shell-buf (get-buffer "*shell*"))
(shell-window ; non-nil when currently displayed
(if shell-buf (get-buffer-window shell-buf t)))
(shell-frame
(if shell-window (window-frame shell-window)))
(in-shell (eq (current-buffer) shell-buf))
(vertical (string= display 'vertical))
(popup-frame (or (string= display 'frame)
(and inject-cd (not (bufferp shell-buf)))
(and (framep shell-frame)
(not (eq shell-frame (selected-frame)))))))
;; With prefix-arg close shell, restore windows. Otherwise (no prefix-arg)
;; toggle shell window; restore windows when called twice in a row, or the
;; current buffer is the shell buffer (`in-shell').
(if current-prefix-arg
(if (bufferp shell-buf)
(progn (message "Exiting shell '%s'" (buffer-name shell-buf))
(kill-buffer shell-buf)
(if in-shell (--toggle-shell-restore-last-conf)))
(error "No shell buffer to kill."))
;; If already in shell-buffer toggle back to stored frame-configuration.
(if (and in-shell (not inject-cd))
(progn
(--toggle-shell-restore-last-conf)
;; Recurse to reopen the shell-buffer in a dedicated frame, or
;; close the dedicated frame and reopen the buffer in a window.
(if (and popup-frame (eq shell-frame (selected-frame)))
(--toggle-shell 'frame inject-cd)
(when (and popup-frame shell-frame)
(delete-frame shell-frame)
(--toggle-shell nil inject-cd))))
;; Not in shell buffer. Warp to it or create new one.
(unless in-shell
(--toggle-shell-store-last-conf))
(if popup-frame
(progn (switch-to-buffer-other-frame (or shell-buf "*shell*"))
(raise-frame
(or shell-frame (window-frame (get-buffer-window "*shell*" t)))))
(if (> (count-windows) 1)
(delete-other-windows)))
;; Finally `cd' into the working directory the current buffer.
(let ((new-shell (not (bufferp shell-buf)))
(new-dir ; `default-directory' of `--toggle-shell-last-buf'
(if --toggle-shell-last-buf
(buffer-local-value 'default-directory --toggle-shell-last-buf))))
;; Open shell, move point to end-of-buffer. The new shell-buffer's
;; `default-directory' will be that of the buffer the shell was
;; launched from.
(when vertical
(if (> (count-windows) 1)
(delete-other-windows))
(split-window-vertically) (other-window 1))
(funcall 'shell)
(when new-shell
(message "New shell %s (%s)" (buffer-name (current-buffer)) new-dir)
(if inject-cd (sit-for 2))) ; wait for prompt
(goto-char (point-max))
;; If on a command-prompt insert and launch a "cd" command (assume no
;; job is running).
(when (and inject-cd new-dir)
(save-excursion
(backward-line-nomark) (end-of-line)
(unless (setq inject-cd (re-search-forward comint-prompt-regexp (point-max) t))
(error "Cannot `pushd', shell is busy")))
(when (and inject-cd)
(let* ((cmd (format
"pushd '%s' %s" (comint-quote-filename new-dir)
(if (buffer-file-name --toggle-shell-last-buf)
(format "# '%s'" (file-name-directory (buffer-file-name --toggle-shell-last-buf)))
""))))
;; `shell-process-cd' set new `default-directory' and set
;; `shell-last-dir' to old. (If the pushd command is
;; successful, a dirs is performed as well; >nul discards this
;; output.)
(shell-process-cd new-dir)
(insert cmd)
(comint-send-input)
(message "%s: cd '%s'" (buffer-name --toggle-shell-last-buf) new-dir))
)
)
)
)
)
)
)
--toggle-shell is the function that does the trick. I bind it to F12:
;; F12 toggle between shell buffer and current window configuration
;; SHIFT-F12 like before, but let shell buffer appear in a dedicated frame
;; ALT-F12 inject a pushd to change to directory of current buffer
;; CTRL-F12 `shell-command'
(global-set-key [(f12)] '--toggle-shell)
(global-set-key [(shift f12)] '(lambda()(interactive)(--toggle-shell 'frame)))
(global-set-key [(meta f12)] '(lambda()(interactive)(--toggle-shell nil t)))
(global-set-key [(meta f10)] '(lambda()(interactive)(--toggle-shell nil t)))
(global-set-key [(control f12)] 'shell-command) ; alias M-!
This is a significant bunch of code to be posted here. But it shall work well.
Semi related - you can quickly run a shell command on selected file with
M+shift+!
It saves a lot of time for smaller commands chmod etc
And maybe my quick pop-up shell also might help you. A quick pop-up shell for emacs
Ecb + eshell will be what you want exactly!
I use vi, but hope this helps. I can open as many terminals as I want by (eg. in Ubuntu 16.04):
ctrl + alt + t
I usually open 2 terminals, and move (position) one terminal to the right by:
ctrl + super + right-arrow
and move the other terminal to the left by:
ctrl + super + left-arrow
so that I have a divided screen by 2 terminals.

in Emacs, what's the best way for keyboard-escape-quit not destroy other windows?

EDIT: I understand there is keyboard-quit (which is normally bound to C-g); but I'm more interested to know about how one deals with editing functions that come with Emacs (like in this case). I run into this kind of situations from time to time when I want to change just a little bit of some build-in functions.
In emacs, when you hit M-ESC ESC (or ESC three times), you can get out of a lots of situations like transient-mark, etc. But I habitually hit the escape key (I actually remap this to a single hit of the escape key) more than I intended, and that ends up killing my windows configuration, which is quite annoying. The function keyboard-escape-quit is defined in simple.el:
(defun keyboard-escape-quit ()
"Exit the current \"mode\" (in a generalized sense of the word).
This command can exit an interactive command such as `query-replace',
can clear out a prefix argument or a region,
can get out of the minibuffer or other recursive edit,
cancel the use of the current buffer (for special-purpose buffers),
or go back to just one window (by deleting all but the selected window)."
(interactive)
(cond ((eq last-command 'mode-exited) nil)
((> (minibuffer-depth) 0)
(abort-recursive-edit))
(current-prefix-arg
nil)
((and transient-mark-mode mark-active)
(deactivate-mark))
((> (recursion-depth) 0)
(exit-recursive-edit))
(buffer-quit-function
(funcall buffer-quit-function))
((not (one-window-p t))
(delete-other-windows))
((string-match "^ \\*" (buffer-name (current-buffer)))
(bury-buffer))))
And I can see that I don't want the lines:
((not (one-window-p t))
(delete-other-windows))
But what is the best way to modify this function? I can see only two ways: 1) modify simple.el 2) copy this function to my .emacs file and do the modifications there. Both ways are not really good; ideally I would like to see something on the line of defadvice, but I can't see how I can do it in this case.
You could use around advice and redefine the offending function to do what you want (i.e. one-window-p should always return t):
(defadvice keyboard-escape-quit (around my-keyboard-escape-quit activate)
(let (orig-one-window-p)
(fset 'orig-one-window-p (symbol-function 'one-window-p))
(fset 'one-window-p (lambda (&optional nomini all-frames) t))
(unwind-protect
ad-do-it
(fset 'one-window-p (symbol-function 'orig-one-window-p)))))
This kind of acts like a (let ...) but has to be more complicated because you need to override a function for a limited scope instead of a variable.
I usually find that 'keyboard-quit (C-g) works to get out of all of those situations.
However, if you really want to have a variant of this function, I think that copying to your .emacs file (and renaming, I usually usa a prefix of bp) and making the edits there is probably the best option.
EDIT, in response to edit: In general, whenever I want an edited version of an emacs function, I either write it myself, or copy it to my .emacs, rename it bp-whotever and then do appropriate edits.
The downside of this is that my .emacs is HUGE, and probably extra-crufty with ancient functions that are nolonger used... the upside is that whenever I need to write something new, I've got tons of sample code to look at...
Here's another, simpler piece of advice that takes advantage of the fact that keyboard-escape-quit calls buffer-quit-function before closing windows:
(defadvice keyboard-escape-quit
(around keyboard-escape-quit-dont-close-windows activate)
(let ((buffer-quit-function (lambda () ())))
ad-do-it))
Works with Emacs 25.1. (I originally used #scottfrazer's advice, but it's unhappy in 25.1. Haven't bothered debugging yet.)
A single press of the Escape key, by default, acts as a Meta prefix key; that is, a keybinding which involves the Meta key.
Triple-pressing the Escape key will run keyboard-escape-quit, which is like keyboard-quit but with more of a "do what I mean" behaviour.
This code may help with your use case. You can use this in your Emacs init file:
;;; esc always quits
(define-key minibuffer-local-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-ns-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-completion-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-must-match-map [escape] 'minibuffer-keyboard-quit)
(define-key minibuffer-local-isearch-map [escape] 'minibuffer-keyboard-quit)
(global-set-key [escape] 'keyboard-quit)
I'm more interested to know about how one deals with editing functions that come with Emacs (like in this case). I run into this kind of situations from time to time when I want to change just a little bit of some build-in functions.
This is exactly the purpose for which I created the library el-patch. You would put this in your init-file:
(el-patch-defun keyboard-escape-quit ()
"Exit the current \"mode\" (in a generalized sense of the word).
This command can exit an interactive command such as `query-replace',
can clear out a prefix argument or a region,
can get out of the minibuffer or other recursive edit,
cancel the use of the current buffer (for special-purpose buffers),
or go back to just one window (by deleting all but the selected window)."
(interactive)
(cond ((eq last-command 'mode-exited) nil)
((> (minibuffer-depth) 0)
(abort-recursive-edit))
(current-prefix-arg
nil)
((and transient-mark-mode mark-active)
(deactivate-mark))
((> (recursion-depth) 0)
(exit-recursive-edit))
(buffer-quit-function
(funcall buffer-quit-function))
(el-patch-remove
((not (one-window-p t))
(delete-other-windows)))
((string-match "^ \\*" (buffer-name (current-buffer)))
(bury-buffer))))
Here's a new way using cl-lib instead of cl which is now deprecated:
;; Make it so keyboard-escape-quit doesn't delete-other-windows
(defadvice keyboard-escape-quit
(around keyboard-escape-quit-dont-delete-other-windows activate)
(cl-letf (((symbol-function 'delete-other-windows)
(lambda () nil)))
ad-do-it))
You'll need to make sure prior to it you have called:
(require 'cl-lib)