Skip to content

enricoflor/ef-ext

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ef-ext.el

;;; 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))

Content

Commenting

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)

Unfilling

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)

Window navigation

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.

Shift current window

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.

./images/shift_01.gif

(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)

Cycle all the windows

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.

./images/cycle_01.gif

(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)

Switch to buffer in other tab

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))))))

Deleting white space intelligently

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.

./images/delete-white-space.gif

(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)

New lines on same column

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.

./images/same-col.gif

(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)

PDF navigation

Window scrolling

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)

Synctex

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)

Magit-remind

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)

LaTeX related stuff

Better handling of references and labels

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)

Housekeeping

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

About

Miscellanea of simple extensions for GNU Emacs

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages