;;; ef-ext.el --- Miscellanea of simple extensions -*- lexical-binding: t; -*-
;; Copyright (C) 2022, 2023 Enrico Flor
;; Author: Enrico Flor <[email protected]>
;; Maintainer: Enrico Flor <[email protected]>
;; URL: https://github.com/enricoflor/ef-ext
;; Version: 0.2.0
;; Package-Requires: ((emacs "27.1"))
;; SPDX-License-Identifier: GPL-3.0-or-later
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version. This program 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 program. If not, see
;; <https://www.gnu.org/licenses/>.
;;; Commentary:
;; ef-ext.el is not a package for Emacs and won't become one. It is
;; just a small (maybe growing) collection of small adjustments I made
;; to existing functionalities (either built in or provided by third
;; party packages). It is essentially a subset of my personal
;; init.el.
;;; Code:
(require 'cl-lib)
(eval-when-compile
(require 'subr-x))
- Commenting
- Unfilling
- Window navigation
- Deleting white space intelligently
- New lines on same column
- PDF navigation
- Magit-remind
- LaTeX related stuff
The default behavior of comment-dwim
has a tiny wrinkle. When the
region is not active and point is on a non commented portion of text,
comment-dwim
adds an inline comment. I don’t do inline comments very
often. So I prefer to have this behavior only if point is already at
the end of the line. If point is anywhere else (and the region is not
active), I want to comment or uncomment the line I am currently on.
(defun ef-ext-comment-line-or-dwim (arg)
"Comment or uncomment line if point is not at end of line.
If point is at the end of line or region is active, evaluate
`comment-dwim' with arguments ARG."
(interactive "*P")
(if (or (eolp) (use-region-p))
(comment-dwim arg)
(comment-or-uncomment-region (line-beginning-position)
(line-end-position))))
;; This will overwrite the default keybinding of comment-dwim
(define-key global-map (kbd "M-;") #'ef-ext-comment-line-or-dwim)
I never use visual-line-mode
and my lines are short as a result (72 or
80 depending on the mode). When I need to yank text from Emacs to
outside of Emacs I often need to unfill the text to have a nice
display of the text. By advising the pertinent commands, I achieve
that by passing the universal argument kill-ring-save
and kill-region
(thus, with C-u M-w
and C-u C-w
). The dependency on unfill only saves
few lines of code in the function, but the package is very small and
independently useful anyways.
(require 'unfill)
(defun ef-ext-unfill-newest-entry-in-kill-ring (origf &rest args)
"Call ORIGF with ARGS.
If called with a prefix argument, unfill newest entry in the
`kill-ring'. This is meant to be an advice around
`kill-ring-save' and `kill-region' (which would then be ORIGF)."
(interactive (list (mark) (point) 'region) p)
(apply origf args)
(when current-prefix-arg
(kill-new
(with-temp-buffer
(insert (car kill-ring))
(unfill-region (point-min) (point-max))
(buffer-string)))))
(advice-add 'kill-ring-save
:around #'ef-ext-unfill-newest-entry-in-kill-ring)
(advice-add 'kill-region
:around #'ef-ext-unfill-newest-entry-in-kill-ring)
I mostly move through windows with the other-window
command, which I
rebound to M-o
. But sometimes I just want the buffer I am visiting to
be in a different window in the current configuration. But at the
same time, I want to keep seeing all the buffers I am currently seeing
in the frame. To avoid reasoning about geometry (move this to the
left, swap this to the right), or moving to different windows and
switching buffer multiple times, I have to functions that take care of
stuff for me.
This is the one I use most often, and I bind M-p
to it (my M-o
is
bound to other-window
). Suppose the selected buffer is b1 in window
w1. By calling ef-ext-shift-window
, b1 will be displayed in the next
window (say w2) and the buffer b2 that was displayed in w2 will
instead be displayed in w1. The geometry of the windows is preserved,
and the selected buffer is still b1, but in window w2. If I call
ef-ext-shift-window
again (without calling any other command in
between), b2 will be back in w2 and this time b1 will be displayed in
w3 (the second next window from w1), and of course b3, which was
displayed in w3, will be displayed in w1. In other words, this
command moves the current buffer to another window (you’d often want
it moved to a larger one) but preserving the windows configuration as
much as possible.
(defvar-local ef-ext-last-shifted-buffer nil)
(defun ef-ext-shift-window ()
"Move current buffer in the frame.
First swap current window with next window. Upon repeating the
command, put what was next window back in place, and swap current
window with the another window, and so on."
(interactive)
(unless (eq last-command 'ef-ext-shift-window)
(setq ef-ext-last-shifted-buffer nil))
(let ((target (window-buffer (next-window))))
(when (and (eq last-command 'ef-ext-shift-window)
(not (eq target ef-ext-last-shifted-buffer)))
(window-swap-states (selected-window)
(get-buffer-window ef-ext-last-shifted-buffer nil)))
(window-swap-states (selected-window) (get-buffer-window target))
(setq ef-ext-last-shifted-buffer target)))
(define-key global-map (kbd "M-p") #'ef-ext-shift-window)
This command simply cycles the windows in the current frame: for each
buffer b displayed in window w, ef-ext-cycle-window
will return a window
configuration where b is displayed in w’/ (/w’/ being the previous window
with respect to /w). Window geometry is preserved and the selected
buffer is unchanged.
(defun ef-ext-cycle-windows ()
"Scramble windows in current frame.
Change selected window, so that current buffer stays the same."
(interactive)
(let ((buff (current-buffer))
(wb-alist (cl-loop for w in (window-list)
collect
(cons w (window-buffer (previous-window
w 0 nil))))))
(dolist (ass wb-alist)
(window--display-buffer (cdr ass) (car ass) 'reuse))
(select-window (get-buffer-window buff nil) t)))
(define-key global-map (kbd "M-P") #'ef-ext-cycle-windows)
This command lets you choose the name of an existing buffer, with
completion, and brings you to the tab (in the sense of tab-bar-mode
,
built in since Emacs 27.1) in which that buffer is live. If this
buffer is not live in any tab, just switch to that buffer in the same
way as switch-to-buffer
would.
(defun ef-ext-switch-to-tab-displaying-buffer ()
"Switch to buffer and possibly switch tab too.
Select a live buffer just like with `switch-to-buffer'. If this
buffer is displayed in a window in the current tab, select that
window. If it is displayed in another tab, switch to that tab
and select the window that displays it. Otherwise just display
that buffer as `switch-to-buffer' would."
(interactive)
(let* ((target-buffer (read-buffer-to-switch
"Switch to windows displaying: "))
(target-tab (tab-bar-get-buffer-tab target-buffer)))
(cond ((not target-tab)
(switch-to-buffer target-buffer))
((equal (car target-tab) 'current-tab)
(select-window (get-buffer-window target-buffer)))
(t
(tab-bar-switch-to-tab
(alist-get 'name (tab-bar-get-buffer-tab target-buffer)))
(select-window (get-buffer-window target-buffer))))))
Emacs already has commands that deal with deleting space and blank
lines rather intelligently. These are delete-blank-lines
(C-x C-o
)
and cycle-spacing
(no keybinding by default). These take care,
respectively, of collapsing several blank lines into one and of
cycling through states where the white space around point is collapsed
into one, deleted altogether and restored as it was.
I want a command that does all this in a Do What I Mean fashion, and
more. The command ef-ext-delete-space-dwim
takes care of this, but
also joins lines that are separated by blank lines. All I need to do
is to repeat the command: when there is no white space left to get rid
of, repeating ef-ext-delete-space-dwim
once more just restores things
as they were before I started calling the command.
I rebind M-SPC
, a very accessible key that is bound to just-one-space
by default, to this new command.
(defvar ef-ext--delete-space-context nil)
(defun ef-ext-delete-space-dwim ()
"Do What I Mean with whitespace.
This command will intelligently delete whitespace vertically and
horizontally. The behavior is cyclic: it will do the next
intelligent deletion upon immediate repetition of the command.
The last step of the cycle, when there is nothing left to delete,
is to restore things as they where right before the cycle was
started."
(interactive)
(when (= (point-min) (point-max))
(user-error "Available portion of buffer is empty"))
(unless (and ef-ext--delete-space-context
(equal last-command this-command))
;; assign a new value to 'ef-ext--delete-space-context'. The value
;; is a cons cell (ST . P), where P is the point and ST is the
;; buffer substring consisting of whitespace (and newline
;; characters) around point.
(let ((beg (save-excursion (skip-chars-backward " \t\n")
(point)))
(end (save-excursion (skip-chars-forward " \t\n")
(point))))
(setq ef-ext--delete-space-context
(cons (buffer-substring beg end) (point)))))
(let* ((aggr (bound-and-true-p aggressive-indent-mode))
(p-line (string-blank-p (save-excursion (forward-line -1)
(thing-at-point 'line t))))
(n-line (string-blank-p (save-excursion (forward-line 1)
(thing-at-point 'line t))))
(ws-len (length (thing-at-point 'whitespace t))))
(when aggr (aggressive-indent-mode -1))
(cond ((string-blank-p (thing-at-point 'line t)) (delete-blank-lines))
((> ws-len 1) (just-one-space))
((and (eolp) n-line) (save-excursion
(forward-line 1)
(delete-blank-lines)))
((eolp) (join-line t))
((and (bolp) p-line) (save-excursion
(forward-line -1)
(delete-blank-lines)))
((bolp) (join-line))
((= ws-len 1) (delete-horizontal-space))
((and (equal last-command this-command)
ef-ext--delete-space-context)
(insert (car ef-ext--delete-space-context))
(goto-char (cdr ef-ext--delete-space-context))
(setq ef-ext--delete-space-context nil)))
(when aggr (aggressive-indent-mode 1))))
(define-key global-map (kbd "M-SPC") #'ef-ext-delete-space-dwim)
This function will create a new line above or after the current one and move the cursor there at the same column position. The line is only created if there is no suitable empty line already.
This is particularly useful to me when writing plain text and I need to enforce some vertical alignment.
(defun ef-ext--newline-same-column (direction &optional arg)
"Move cursor to a different line on the same column.
This command creates a new blank line if the target is not a
blank line.
Optional numeric argument ARG specifies how many lines to skip,
if nil it defaults to 1 (just like the first argument of
`next-line').
If DIRECTION is 'up move cursor up, if 'down move it down."
(let* ((col (current-column))
(how-much (or arg 1))
(count 1)
(down (or (and (eq direction 'down)
(> arg 0))
(and (eq direction 'up)
(< arg 0))))
(step (if down 1 -1)))
(when (eq arg 0)
(user-error "0 is an Invalid value for ARG"))
;; If ARG has a negative value, make how-much be its absolute
(when (< how-much 0) (setq how-much (- how-much (* 2 how-much))))
(while (<= count how-much)
(cond ((save-excursion (and (ignore-errors (line-move step))
(string-blank-p (thing-at-point 'line t))))
(line-move step)
(move-to-column col t))
;; If we are at one end of the available portion of the buffer
;; we have to "create" new lines
(down
(end-of-line)
(split-line t)
(next-line)
(delete-region (line-beginning-position) (line-end-position))
(move-to-column col t))
(t (beginning-of-line) (split-line t) (move-to-column col t)))
(setq count (1+ count)))))
Two interactive commands are defined: one that creates a line above
and one that create a line below the current one. You can pass an
optional numeric prefix argument to specify how many lines to create
(in a manner similar to how you can pass numeric arguments to, say,
next-line
). I bind M-C-p
and M-C-n
to these commands because they are
very similar to the standard keybindings for next-line
and
previous-line.
(defun ef-ext-newline-up-same-column (&optional arg)
"Open a new line above cursor and move point there on same column.
If the target line is already blank, just put the cursor there at
the right column.
Optional numeric prefix argument ARG specifies how many lines to
move (like the first argument of `next-line')."
(interactive "p")
(ef-ext--newline-same-column 'up arg))
(defun ef-ext-newline-down-same-column (&optional arg)
"Open a new line below cursor and move point there on same column.
If the target line is already blank, just put the cursor there at
the right column.
Optional numeric prefix argument ARG specifies how many lines to
move (like the first argument of `next-line')."
(interactive "p")
(ef-ext--newline-same-column 'down arg))
(define-key global-map (kbd "M-C-p") #'ef-ext-newline-up-same-column)
(define-key global-map (kbd "M-C-n") #'ef-ext-newline-down-same-column)
I use scroll-other-window
and scroll-other-window-down
a lot. But I
also use pdf-tools, and I would like for these commands to work when
the other window is displaying a buffer visiting a pdf. So I advise
the two functions in order to achieve this.
(require 'pdf-view)
(defun ef-ext-scroll-down-other-window-pdf (origf &rest args)
"Call ORIGF with ARGS if other window is not in `pdf-view-mode'.
If it is, scroll down the pdf in other window.
This is meant to be an advice around
`scroll-other-window-down' (fed as ORIGF)."
(interactive)
(when (one-window-p) (user-error "There is no other window"))
(let ((other-window-pdf (prog2
(other-window 1)
(eq major-mode 'pdf-view-mode)
(other-window -1))))
(if (not other-window-pdf)
(apply origf args)
(other-window 1)
(ignore-errors (pdf-view-previous-line-or-previous-page 5))
(other-window -1))))
(defun ef-ext-scroll-up-other-window-pdf (origf &rest args)
"Call ORIGF with ARGS if other window is not in `pdf-view-mode'.
If it is, scroll up the pdf in other window.
This is meant to be an advice around `scroll-other-window' (fed
as ORIGF)."
(interactive)
(when (one-window-p) (user-error "There is no other window"))
(let ((other-window-pdf (prog2
(other-window 1)
(eq major-mode 'pdf-view-mode)
(other-window -1))))
(if (not other-window-pdf)
(apply origf args)
(other-window 1)
(ignore-errors (pdf-view-next-line-or-next-page 5))
(other-window -1))))
(advice-add 'scroll-other-window-down
:around #'ef-ext-scroll-down-other-window-pdf)
(advice-add 'scroll-other-window
:around #'ef-ext-scroll-up-other-window-pdf)
TeX-view
, bound to C-c C-v
, lets me jump from a position in the source
tex file to the corresponding position on the output pdf (thanks to
Synctex). However the default binding to perform the inverse
operation (from the pdf to the tex file) involves using the mouse. I
much prefer using just the keyboard. With the snippet below, I just
move to the desired location on the pdf with isearch
(something I
always do anyways) and press C-RET
before leaving isearch-mode
. Note
that C-RET
is not bound to anything by default in isearch-mode-map
so
there really isn’t any disadvantage here.
(require 'pdf-isearch)
(defun ef-ext-jump-to-source ()
"Try to call `pdf-isearch-sync-backward'.
If that is successful and point is invisible, unfold the current
environment to show it."
(interactive)
(ignore-errors (pdf-isearch-sync-backward))
(when (invisible-p (point)) (outline-show-entry)))
(define-key isearch-mode-map (kbd "C-<return>") #'ef-ext-jump-to-source)
Sometimes when working on things you forget that certain files in the
repository were modified. It’s good to at least be reminded of that
before moving on. Adding ef-ext-magit-remind-on-kill-buffer-hook
on
kill-buffer-hook
does this in a rather unobtrusive way, relying on
magit.
At any time, you might have several buffers open that visit files in a given repository R. Now suppose you start killing these buffers (one by one, or in batch): at the moment where no buffer is left that visits a non-ignored file in R, the magit status buffer will be displayed if anything is modified (staged or unstaged) in R, so that you can do what you need to do, or ignore everything.
(require 'magit)
(defun ef-ext--buffer-in-repo-p (repo buffer)
"Return t if BUFFER visits a file in REPO.
REPO, a string, is a the absolute path of the git repository.
Return nil if BUFFER visits a file that is ignored by git (that
is, in the output of `magit-ignored-files')."
(when-let ((file (buffer-file-name buffer))
(root (ignore-errors
(expand-file-name (vc-find-root file ".git")))))
(and (string-equal repo root)
(not (member file
(mapcar (lambda (x) (concat repo x))
(magit-ignored-files)))))))
(defun ef-ext--magit-reminder-needed-p (&optional buffer)
"Return nil if no reminder is needed.
If BUFFER (a buffer) is nil, use value of `current-buffer'.
If BUFFER is visiting a file in a certain git repository such
that:
- the file it is visiting is not ignored by git
and
- no other buffer is currently visiting a non ignored file in
that repository
return the absolute path of the repository as a string."
(when-let* ((dir default-directory)
(buff (or buffer (current-buffer)))
(file (buffer-file-name buff))
(repo (ignore-errors
(expand-file-name (vc-find-root file ".git"))))
(modifiedp (prog2
(cd repo)
(magit-anything-modified-p)
(cd dir)))
(bufflist
(cl-remove-if-not
(lambda (x) (ef-ext--buffer-in-repo-p repo x))
(buffer-list))))
(if (remove buff bufflist) nil repo)))
(defun ef-ext-magit-remind-on-kill-buffer-hook ()
"Display magit status buffer if the reminder is needed.
The reminder is needed if `ef-ext--magit-reminder-needed-p'
returns non-nil.
This function is meant to be added on `kill-buffer-hook'."
(when-let ((repo (ef-ext--magit-reminder-needed-p)))
(magit-status-setup-buffer repo)
(message (format "Modified files in repo %s"
(abbreviate-file-name repo)))))
(add-hook 'kill-buffer-hook 'ef-ext-magit-remind-on-kill-buffer-hook)
RefTeX
is excellent, but there is one thing that I use rather often
that is missing from it, namely the ability to easily change labels in
a way that doesn’t cause trouble. Since I always use reftex-label
and
reftex-reference
, I want to define two commands that behave exactly
like them if point is not on a reference or on a label (i.e. if point
is in a place where it is natural to invoke these commands), and let’s
you rename the label otherwise.
First, we need a function that tells us whether point is on a reference or label macro or not:
(require 'reftex)
(defconst ef-ext-label-ref-rg "\\\\\\(?1:[Rr]ef\\|label\\){\\(?2:[^}]*\\)}"
"Regexp matching label or reference items in LaTeX syntax.")
(defun ef-ext-point-on-label-ref-p ()
"Return NIL if point is not on a LaTeX \\ref or \\label.
Otherwise, return the label of the item at point."
(let ((position (point)))
(save-excursion
(beginning-of-line)
(catch 'found
(while (re-search-forward ef-ext-label-ref-rg (line-end-position) t)
(when (<= (match-beginning 0) position (match-end 0))
(throw 'found
(buffer-substring-no-properties (match-beginning 2)
(match-end 2))))
nil)))))
Then we define this macro:
(defmacro ef-ext-do-on-all-files (file-list &rest body)
"Execute BODY on all files in FILE-LIST.
FILE-LIST is a list of strings (the absolute path of each file).
New buffers created to visit a file previously visited by no
buffer are killed only if they were not modified by evaluating
BODY in them."
(save-excursion
`(dolist (f ,file-list)
(if (find-buffer-visiting f)
(with-current-buffer (find-buffer-visiting f)
,@body)
(let ((b (find-file-noselect f)))
(with-current-buffer b
,@body)
(unless (buffer-modified-p b)
(kill-buffer b)))))))
(defun ef-ext-get-ref-label-argument ()
"Return nil if point is not on a label or ref macro.
If point is on such a macro, return a cons cell
(N . L)
where N, a string, is the name of the macro and L, a string, is
the argument of the macro."
(let ((name (TeX-current-macro)))
(when (member name '("label" "ref" "Ref"))
(let ((start (car (TeX-find-macro-boundaries)))
lab-start)
(save-excursion
(goto-char start)
(search-forward (concat "\\" name))
(while (TeX-in-comment) (forward-char 1))
(while (looking-at-p "[[:space:]]\\|\n") (forward-char 1))
(unless (looking-at-p "{") (error ""))
(setq lab-start (1+ (point)))
(forward-sexp)
(forward-char -1)
(cons name (buffer-substring-no-properties lab-start (point))))))))
(defun ef-ext-existing-labels-refs ()
"Return alist of labels and references in the LaTeX project."
(let ((files (ignore-errors (reftex-all-document-files))))
(unless files
(user-error "Buffer is not visiting a LaTeX file"))
(let (labs
refs
(re "\\\\\\([Rr]ef\\|label\\)"))
(ef-ext-do-on-all-files
files
(widen)
(goto-char (point-min))
(while (TeX-re-search-forward-unescaped re nil t)
(let ((result (ef-ext-get-ref-label-argument)))
(if (string-equal "label" (car result))
(push (cdr result) labs)
(push (cdr result) refs)))))
(list (cons 'labels (delete-dups labs))
(cons 'references (delete-dups refs))))))
Below are the two functions. Note that ef-ext-reftex-label
accepts a
prefix argument: if called this way (i.e., with the keybinding defined
below, with C-u C-c )
, this command lets you select a label from the
set of labels that are already used in a \ref{}
macro anywhere in the
(potentially multi-file) project. The reason I find this useful is
that sometimes I find myself writing a \ref{}
macro before I write the
corresponding \label{}
macro, and this way I can just choose the label
immediately (by calling ef-ext-reftex-reference
with a prefix
argument) and then put it back in the \label{}
macro.
(defun ef-ext-reftex-label (&optional arg)
"Evaluate `reftex-label' if not on a LaTeX \\ref or \\label.
Otherwise, prompt for new label and relabel all associated items
in the buffer.
If prefix argument ARG is non-nil, prompt for a string to be used
as a label, with completion: the candidates are the labels
currently referenced in the project."
(interactive "P")
(let ((on-macro (ef-ext-get-ref-label-argument)))
(cond (on-macro
(save-excursion
(let* ((old-label (cdr on-macro))
(new-label (read-string "New label: "
old-label nil nil t)))
(when (string-blank-p new-label)
(user-error "Labels can't be empty"))
(when (assoc new-label
(symbol-value reftex-docstruct-symbol))
(unless (yes-or-no-p
(format "Label \"%s\" already used. Continue? "
new-label))
(ef-ext-reftex-label)))
(reftex-parse-all)
(ef-ext-do-on-all-files
(reftex-all-document-files)
(widen)
(goto-char (point-min))
(while (re-search-forward ef-ext-label-ref-rg nil t)
(when (equal (match-string-no-properties 2)
old-label)
(replace-match new-label t t nil 2))))
(reftex-parse-all))))
(current-prefix-arg
(let* ((existing (save-excursion (ef-ext-existing-labels-refs)))
(good-labels (cl-remove-if
(lambda (x)
(member x (cdr (assoc 'labels existing))))
(cdr (assoc 'references existing))))
(chosen-label (completing-read "Label: " good-labels)))
(insert "\\label{" chosen-label "}")))
(t
(reftex-label)))))
(defun ef-ext-reftex-reference (&optional arg)
"Evaluate `reftex-reference' if not on a LaTeX \\ref or \\label.
Otherwise, prompt for new label and relabel all associated items
in the buffer.
If prefix argument ARG is non-nil, let the user input a label for
the reference (that is, an argument for the \\ref macro) instead
of offering one among the ones already used in the document."
(interactive "P")
(let ((on-macro (ef-ext-get-ref-label-argument)))
(cond (on-macro
(save-excursion
(let* ((old-label (cdr on-macro))
(new-label (read-string "New label: "
old-label nil nil t)))
(when (string-blank-p new-label)
(user-error "Labels can't be empty"))
(when (assoc new-label
(symbol-value reftex-docstruct-symbol))
(unless (yes-or-no-p
(format "Label \"%s\" already used. Continue? "
new-label))
(ef-ext-reftex-label)))
(reftex-parse-all)
(ef-ext-do-on-all-files
(reftex-all-document-files)
(widen)
(goto-char (point-min))
(while (re-search-forward ef-ext-label-ref-rg nil t)
(when (equal (match-string-no-properties 2)
old-label)
(replace-match new-label t t nil 2))))
(reftex-parse-all))))
(current-prefix-arg
(let ((new-label (read-string "Label: "
"" nil nil t)))
(when (string-blank-p new-label)
(user-error "Labels can't be empty"))
(when (assoc new-label
(symbol-value reftex-docstruct-symbol))
(unless (yes-or-no-p
(format "Label \"%s\" already used. Continue? "
new-label))
(user-error "")))
(insert "\\ref{" new-label "}")))
(t
(reftex-parse-one)
(reftex-reference)))))
I overwrite the default bindings, since these two functions are drop in replacement for the original ones.
(define-key reftex-mode-map (kbd "C-c (") #'ef-ext-reftex-label)
(define-key reftex-mode-map (kbd "C-c )") #'ef-ext-reftex-reference)
One of the things I never remember to do is to escape the space after
the period when the period does not mark the end of a sentence.
Typically, this should be done after abbreviations: I add these to a
constant ef-ext-abbreviations-to-escape
.
(defconst ef-ext-abbreviations-to-escape '("s.t."
"e.g."
"i.e."
"cf."
"cfr."
"etc."
"viz."
"et al."
"a.o."
"z.B."
"bzw.")
"Escape space after these strings in LaTeX.")
A function on before-save-hook
will take care of escaping these spaces
for me (unless they are already escaped). While I’m at it, I will let
this function remove certain tildes (unbreakable spaces) added by
RefTeX
when adding references between parentheses. There is certainly
a way to prevent RefTeX
from doing it, but it is much easier to just
remove these things directly.
(defun ef-ext-latex-housekeeping ()
"Remove unwanted tildes, escape abbreviations, fill buffer."
(when (eq major-mode 'latex-mode)
(let ((re (concat "\\_<\\(?1:"
(regexp-opt ef-ext-abbreviations-to-escape)
"\\)[[:space:]]")))
(save-excursion
(goto-char (point-min))
(while (re-search-forward "\\((\\)~\\(\\\\ref\\|\\\\Ref\\)" nil t)
(replace-match "\\1\\2"))
(goto-char (point-min))
(while (TeX-re-search-forward-unescaped re nil t)
(goto-char (match-end 1))
(insert "\\"))))))
(add-hook 'before-save-hook #'ef-ext-latex-housekeeping)
(provide 'ef-ext)
;;; ef-ext.el ends here