How can you modify two matching delimiters at once with Emacs? - emacs

While this question concerns the formatting of LaTeX within Emacs (and maybe Auctex), I believe this can be applied to more general situations in Emacs concerning delimiters like parentheses, brackets, and braces.
I am looking to be able to do the following with Emacs (and elisp), and don't know where to begin. Say I have:
(This is in parentheses)
With some keybinding in Emacs, I want Emacs to find the matching delimiter to whichever one is by my cursor (something I know Emacs can do since it can highlight matching delimiters in various modes) and be able to change both of them to
\left( This is in parentheses \right)
The delimiters I would like this to work with are: (...), [...], \lvert ... \rvert, \langle ... \rangle, \{ ... \}. What elisp would I need to accomplish this task?
More general ways to handle matching delimiters are welcome.

Evaluate the command below in Emacs. After reloading you can put the point (text cursor) immediately after a closing paren. Then do M-x replace-matching-parens to replace the closing ) with \right) and the matching start paren ( with \left(.
(defun replace-matching-parens ()
(interactive)
(save-excursion
(let ((end-point (point)))
(backward-list)
(let ((start-point (point)))
(goto-char end-point)
(re-search-backward ")" nil t)
(replace-match " \\\\right)" nil nil)
(goto-char start-point)
(re-search-forward "(" nil t)
(replace-match "\\\\left( " nil nil)))))
The interactive bit indicates that I want a "command", so it can be executed using M-x. To avoid the cursor ending up in a strange place after execution I'm wrapping the logic in save-excursion. The point jumps back to the opening paren using backward-list and holds on to the start and end positions of the paren-matched region. Lastly, starting at the end and working backwards I replace the strings. By replacing backwards rather than forwards I avoid invalidating end-point before I need it.
Generalizing this to handle different kinds of delimiters shouldn't be too bad. backward-list ought to work with any pair of strings emacs recognizes as analogues of ( and ). To add more parenthesis-like string pairs, check out set-syntax-table in this Parenthesis Matching article.
Use global-set-key to setup a key binding to replace-matching-parens.
Fair warning: replace-matching-parens is the first elisp command I've implemented, so it may not align with best practices. To all the gurus out there, I'm open to constructive criticism.

Related

Emacs: Prevent auto-fill mode breaking lines in latex \text{...} inline commands

The latex package polyglossia allows for correct typesetting of foreign languages: it provides an inline command of the form \text[foreign_language]{...}, such as \textspanish{Soy el hijo de Fernando}.
Since I use Emacs, I have to prevent auto-fill mode from breaking up those code blocks: for example, ending a line with \textspanish{Soy el, and beginning the next line with hijo de Fernando}. When auto-fill mode breaks lines in this way, the exporter gets confused.
I tried creating a function to add as hook to fill-nobreak-predicate, but my knowledge of regular expressions and elips is not good enough. This is how far I got:
(defun foreign-language-nobreak-p ()
(or (looking-at "[[[:space:]]\|[[:print:]]].*}")
(save-excursion
(skip-chars-backward " \t")
(unless (bolp)
(backward-char 1)
(looking-at ".*\\text")))))
(add-hook 'fill-nobreak-predicate #'foreign-language-nobreak-p)
Any ideas on what went wrong?
First of all, thanks for pointing out fill-nobreak-predicate. Never heard about it in my first 23 years of Emacs.
Regarding your regexp question, I'd like to mention the function regexp-opt which takes a list of strings and builds an efficient regexp that matches those strings:
(defvar foreign-lang-re
(regexp-opt
'("\\textspanish{"
"\\textrussian{"
"\\textfrench{")))
If you factor out the supported languages into yet another variable, you could also build the list of strings with a loop, adding \text and the trailing {.
If your heuristic would be stable enough that you don't want auto filling to kick in when there is just the opening command somewhere on the current line, you could use thing-at-point like so:
(defun foreign-language-nobreak-p ()
(string-match
foreign-lang-re
(thing-at-point 'line t)))
This does not work when you closed the command already with a }. For that to work better, you'd need to search backward from your current point for the optimized regexp and forward for a closing curly brace, limiting the search to (bolp) and (eolp) respectively. This would get really hairy if you start to use other commands with curly braces inside the \textspanish command, though.
Hope that makes sense and helps a bit.

Emacs - Skip whitespace kills

I'm trying to make the kill ring essentially ignore whitespace only entries (tabs, newlines, just spaces, etC), I'm fairly new to elisp and I'm pretty sure the way to do is by doing defadvice but I have a few questions.
Would it better to stop whitespace entries from ever getting into the kill ring in the first place, or to skip them on yank? I assumed the latter.
In which case, I'm completely lost on which function I should advise, its between current-kill, yank, and insert-for-yank - but am not entirely sure which I should manipulate to not yank whitespace from the kill ring.
Thanks!
EDIT: I'm pretty sure the way to do this is to manipulate `current-kill' to keep calling itself until it reaches a non whitespace entry? (or the end of the ring, whichever comes first)
From the comments it seems that you are having problems with whitespaces in your kill-ring since you kill whitespace lines. My solution would be to avoid killing whitespace lines and to use the function delete-blank-line (C-x C-o) instead. This reduces a group a blank lines (including whitespaces and tabs) to a single blank line.
I wrote a package clean-kill-ring.el that provides functionality for controlling what is allowed in the kill ring.
By default enabling clean-kill-ring-mode prevents blank strings from entering the kill ring, but further customization is possible as well.
The following advice does not let whitespace be added to the kill ring in the first place:
(defun night/h-kill-skip-whitespace (orig-fn string &optional rest)
(let* (
(string-raw (substring-no-properties string))
(space-p (not (string-match-p "[^ \t\n\r]" string-raw))))
(cond
((not space-p)
(apply orig-fn string rest))
(t
(message "skipped whitespace kill")
))))
(advice-add 'kill-new :around #'night/h-kill-skip-whitespace)

emacs equivalent of ct

looking for an equivalent cut and paste strategy that would replicate vim's 'cut til'. I'm sure this is googleable if I actually knew what it was called in vim, but heres what i'm looking for:
if i have a block of text like so:
foo bar (baz)
and I was at the beginning of the line and i wanted to cut until the first paren, in visual mode, I'd do:
ct (
I think there is probably a way to look back and i think you can pass more specific regular expressions. But anyway, looking for some emacs equivalents to doing this kind of text replacement. Thanks.
Here are three ways:
Just type M-dM-d to delete two words. This will leave the final space, so you'll have to delete it yourself and then add it back if you paste the two words back elsewhere.
M-z is zap-to-char, which deletes text from the cursor up to and including a character you specify. In this case you'd have to do something like M-2M-zSPC to zap up to and including the second space character.
Type C-SPC to set the mark, then go into incremental search with C-s, type a space to jump to the first space, then C-s to search forward for the next space, RET to terminate the search, and finally C-w to kill the text you selected.
Personally I'd generally go with #1.
as ataylor said zap-to-char is the way to go, The following modification to the zap-to-char is what exactly you want
(defun zap-up-to-char (arg char)
"Like standard zap-to-char, but stops just before the given character."
(interactive "p\ncZap up to char: ")
(kill-region (point)
(progn
(search-forward (char-to-string char) nil nil arg)
(forward-char (if (>= arg 0) -1 1))
(point))))
(define-key global-map [(meta ?z)] 'zap-up-to-char) ; Rebind M-z to our version
BTW don't forget that it has the ability to go backward with a negative prefix
That sounds like zap-to-char in emacs, bound to M-z by default. Note that zap-to-char will cut all the characters up to and including the one you've selected.

replace-char in Emacs Lisp ?

Emacs Lisp has replace-string but has no replace-char. I want to replace "typographic" curly quotes (Emacs code for this character is hexadecimal 53979) with regular ASCII quotes, and I can do so with:
(replace-string (make-string 1 ?\x53979) "'")
I think it would be better with replace-char.
What is the best way to do this?
This is the way I replace characters in elisp:
(subst-char-in-string ?' ?’ "John's")
gives:
"John’s"
Note that this function doesn't accept characters as string. The first and second argument must be a literal character (either using the ? notation or string-to-char).
Also note that this function can be destructive if the optional inplace argument is non-nil.
Why not just use
(replace-string "\x53979" "'")
or
(while (search-forward "\x53979" nil t)
(replace-match "'" nil t))
as recommended in the documentation for replace-string?
which would certainly be better with replace-char. Any way to improve my code?
Is it actually slow to the point where it matters? My elisp is usually ridiculously inefficient and I never notice. (I only use it for editor tools though, YMMV if you're building the next MS live search with it.)
Also, reading the docs:
This function is usually the wrong thing to use in a Lisp program.
What you probably want is a loop like this:
(while (search-forward "’" nil t)
(replace-match "'" nil t))
This answer is probably GPL licensed now.
What about this
(defun my-replace-smart-quotes (beg end)
"replaces ’ (the curly typographical quote, unicode hexa 2019) to ' (ordinary ascii quote)."
(interactive "r")
(save-excursion
(format-replace-strings '(("\x2019" . "'")) nil beg end)))
Once you have that in your dotemacs, you can paste elisp example codes (from blogs and etc) to your scratch buffer and then immediately press C-M-\ (to indent it properly) and then M-x my-replace-smart-quotes (to fix smart quotes) and finally C-x C-e (to run it).
I find that the curly quote is always hexa 2019, are you sure it's 53979 in your case? You can check characters in buffer with C-u C-x =.
I think you can write "’" in place of "\x2019" in the definition of my-replace-smart-quotes and be fine. It's just to be on the safe side.

How to get Emacs to unwrap a block of code?

Say I have a line in an emacs buffer that looks like this:
foo -option1 value1 -option2 value2 -option3 value3 \
-option4 value4 ...
I want it to look like this:
foo -option1 value1 \
-option2 value2 \
-option3 value3 \
-option4 value4 \
...
I want each option/value pair on a separate line. I also want those subsequent lines indented appropriately according to mode rather than to add a fixed amount of whitespace. I would prefer that the code work on the current block, stopping at the first non-blank line or line that does not contain an option/value pair though I could settle for it working on a selected region.
Anybody know of an elisp function to do this?
Nobody had what I was looking for so I decided to dust off my elisp manual and do it myself. This seems to work well enough, though the output isn't precisely what I asked for. In this version the first option goes on a line by itself instead of staying on the first line like in my original question.
(defun tcl-multiline-options ()
"spread option/value pairs across multiple lines with continuation characters"
(interactive)
(save-excursion
(tcl-join-continuations)
(beginning-of-line)
(while (re-search-forward " -[^ ]+ +" (line-end-position) t)
(goto-char (match-beginning 0))
(insert " \\\n")
(goto-char (+(match-end 0) 3))
(indent-according-to-mode)
(forward-sexp))))
(defun tcl-join-continuations ()
"join multiple continuation lines into a single physical line"
(interactive)
(while (progn (end-of-line) (char-equal (char-before) ?\\))
(forward-line 1))
(while (save-excursion (end-of-line 0) (char-equal (char-before) ?\\))
(end-of-line 0)
(delete-char -1)
(delete-char 1)
(fixup-whitespace)))
In this case I would use a macro. You can start recording a macro with C-x (, and stop recording it with C-x ). When you want to replay the macro type C-x e.
In this case, I would type, C-a C-x ( C-s v a l u e C-f C-f \ RET SPC SPC SPC SPC C-x )
That would record a macro that searches for "value", moves forward 2, inserts a slash and newline, and finally spaces the new line over to line up. Then you could repeat this macro a few times.
EDIT: I just realized, your literal text may not be as easy to search as "value1". You could also search for spaces and cycle through the hits. For example, hitting, C-s a few times after the first match to skip over some of the matches.
Note: Since your example is "ad-hoc" this solution will be too. Often you use macros when you need an ad-hoc solution. One way to make the macro apply more consistently is to put the original statement all on one line (can also be done by a macro or manually).
EDIT: Thanks for the comment about ( versus C-(, you were right my mistake!
Personally, I do stuff like this all the time.
But I don't write a function to do it unless I'll be doing it
every day for a year.
You can easily do it with query-replace, like this:
m-x (query-replace " -option" "^Q^J -option")
I say ^Q^J as that is what you'll type to quote a newline and put it in
the string.
Then just press 'y' for the strings to replace, and 'n' to skip the wierd
corner cases you'd find.
Another workhorse function is query-replace-regexp that can do
replacements of regular expressions.
and also grep-query-replace, which will perform query-replace by parsing
the output of a grep command. This is useful because you can search
for "foo" in 100 files, then do the query-replace on each occurrence
skipping from file to file.
Your mode may support this already. In C mode and Makefile mode, at least, M-q (fill-paragraph) will insert line continuations in the fill-column and wrap your lines.
What mode are you editing this in?