Emacs: delete whitespaces or a word - emacs

How can I configure emacs to work in the same way as other modern editors where pressing Alt+D or Alt+Backspace deletes either adjacent whitespaces or a single word? By default, emacs always deletes a word.

Trough some time of using Emacs I figured that even though I can alter the basic functionality, it usually doesn't pay off much in terms of efficiency. In fact, after I did it several times, I came to regret it and undid it. This is not true all of the time, some keybindings are really uncomfortable or rarely useful, but I don't think this is the case with how kill word works. In fact, I just now realized that: I did try the keybinding in Eclipse, but I've been using it with Emacs-style kebindings since forever...
Anyways, as I just said, before you are "fixing" that functionality, make sure it is really broken :) I never find myself needing the kind of function you describe, and maybe here's why:
M-SPC reduces the space between words to just one space. This is what I would've used if the point was between the words and I wanted to delete the extra space separating the words.
M-\ removes all horizontal space. This will join two words separated by space.
If what you are trying to achieve is some kind of "sparse" formatting, as in:
int foo = 42;
unsigned int bar = 43;
then there's M-xalign-regexp to do that.
I just never happen to have a) long consequent runs of whitepsace, unless it is the indentation, and in the case it is the indentation, TAB usually handles it better. b) even if there are long consequent runs of whitespace, I so rarely move the point by one character at a time, so it's hard to think of a situation where I'd find the point surrounded by several whitespaces. Things like Artist mode, or Dot diagrams come to mind, but it doesn't happen during code editing.
Finally, if you are trying to, well, let's say just edit an arbitrary text file and you want to add or remove horizontal space between words... Again, there's M-xalign-regexp to do that, or you could use commands that operate on rectangles, if those are several lines at the time. Well, Emacs will even recognize the ad hoc tabs and will try to align the text such as to match the last line before the point, when you hit TAB.
Finally, if for some reason I cannot fathom :) I really needed to do exactly what you describe, then I'd do it like so: kM-\BACKSPACE (it can be any other key instead of "k" - it is just right under your finger, so it's fast to type :) Or, if I'm lazy to think about it: M-SPCM-fM-bC-w - maybe sounds like a lot, but these are the commands you would be using all of the time anyway, so it doesn't hinder you in terms of speed.

(defvar movement-syntax-table
(let ((st (make-syntax-table)))
;; ` default = punctuation
;; ' default = punctuation
;; , default = punctuation
;; ; default = punctuation
(modify-syntax-entry ?{ "." st) ;; { = punctuation
(modify-syntax-entry ?} "." st) ;; } = punctuation
(modify-syntax-entry ?\" "." st) ;; " = punctuation
(modify-syntax-entry ?\\ "_" st) ;; \ = symbol
(modify-syntax-entry ?\$ "_" st) ;; $ = symbol
(modify-syntax-entry ?\% "_" st) ;; % = symbol
st)
"Syntax table used while executing custom movement functions.")
(defun delete-word-or-whitespace (&optional arg)
"http://stackoverflow.com/a/20456861/2112489"
(interactive "P")
(with-syntax-table movement-syntax-table
(let* (
beg
end
(word-regexp "\\sw")
(punctuation-regexp "\\s.")
(symbol-regexp "\\s_\\|\\s(\\|\\s)"))
(cond
;; Condition # 1
;; right of cursor = word or punctuation or symbol
((or
(save-excursion (< 0 (skip-syntax-forward "w")))
(save-excursion (< 0 (skip-syntax-forward ".")))
(save-excursion (< 0 (skip-syntax-forward "_()"))))
;; Condition #1 -- Step 1 of 2
(cond
;; right of cursor = word
((save-excursion (< 0 (skip-syntax-forward "w")))
(skip-syntax-forward "w")
(setq end (point))
(while (looking-back word-regexp)
(backward-char))
(setq beg (point))
(delete-region beg end))
;; right of cursor = punctuation
((save-excursion (< 0 (skip-syntax-forward ".")))
(skip-syntax-forward ".")
(setq end (point))
(while (looking-back punctuation-regexp)
(backward-char))
(setq beg (point))
(delete-region beg end))
;; right of cursor = symbol
((save-excursion (< 0 (skip-syntax-forward "_()")))
(skip-syntax-forward "_()")
(setq end (point))
(while (looking-back symbol-regexp)
(backward-char))
(setq beg (point))
(delete-region beg end)))
;; Condition #1 -- Step 2 of 2
(cond
;; right of cursor = whitespace
;; left of cursor = not word / not symbol / not punctuation = whitespace or bol
((and
(save-excursion (< 0 (skip-chars-forward "\s\t")))
(not (save-excursion (> 0 (skip-syntax-backward "w"))))
(not (save-excursion (> 0 (skip-syntax-backward "."))))
(not (save-excursion (> 0 (skip-syntax-backward "_()")))))
(setq beg (point))
(skip-chars-forward "\s\t")
(setq end (point))
(delete-region beg end))
;; right of cursor = whitespace
;; left of cursor = word or symbol or punctuation
((and
(save-excursion (< 0 (skip-chars-forward "\s\t")))
(or
(save-excursion (> 0 (skip-syntax-backward "w")))
(save-excursion (> 0 (skip-syntax-backward ".")))
(save-excursion (> 0 (skip-syntax-backward "_()")))))
(fixup-whitespace))))
;; Condition # 2
;; right of cursor = whitespace
;; left of cursor = bol | left of cursor = whitespace | right of cursor = whitespace + eol
((and
(save-excursion (< 0 (skip-chars-forward "\s\t")))
(or
(bolp)
(save-excursion (> 0 (skip-chars-backward "\s\t")))
(save-excursion (< 0 (skip-chars-forward "\s\t")) (eolp))))
(setq beg (point))
(skip-chars-forward "\s\t")
(setq end (point))
(delete-region beg end))
;; Condition # 3
;; right of cursor = whitespace or eol
;; left of cursor = word or symbol or punctuation
;; not bol + word or symbol or punctuation
;; not bol + whitespace + word or symbol or punctuation
((and
(or (save-excursion (< 0 (skip-chars-forward "\s\t"))) (eolp))
(or
(save-excursion (> 0 (skip-syntax-backward "w")))
(save-excursion (> 0 (skip-syntax-backward ".")))
(save-excursion (> 0 (skip-syntax-backward "_()"))))
(not (save-excursion (> 0 (skip-syntax-backward "w")) (bolp)))
(not (save-excursion (> 0 (skip-syntax-backward ".")) (bolp)))
(not (save-excursion (> 0 (skip-syntax-backward "_()")) (bolp)))
(not (save-excursion (and (> 0 (skip-syntax-backward "w")) (> 0 (skip-chars-backward "\s\t")) (bolp))))
(not (save-excursion (and (> 0 (skip-syntax-backward ".")) (> 0 (skip-chars-backward "\s\t")) (bolp))))
(not (save-excursion (and (> 0 (skip-syntax-backward "_()")) (> 0 (skip-chars-backward "\s\t")) (bolp)))))
(setq end (point))
(cond
((save-excursion (> 0 (skip-syntax-backward "w")))
(while (looking-back word-regexp)
(backward-char)))
((save-excursion (> 0 (skip-syntax-backward ".")))
(while (looking-back punctuation-regexp)
(backward-char)))
((save-excursion (> 0 (skip-syntax-backward "_()")))
(while (looking-back symbol-regexp)
(backward-char))))
(setq beg (point))
(when (save-excursion (> 0 (skip-chars-backward "\s\t")))
(skip-chars-backward "\s\t")
(setq beg (point)))
(delete-region beg end)
(skip-chars-forward "\s\t"))
;; Condition # 4
;; not bol = eol
;; left of cursor = bol + word or symbol or punctuation | bol + whitespace + word or symbol or punctuation
((and
(not (and (bolp) (eolp)))
(or
(save-excursion (> 0 (skip-syntax-backward "w")) (bolp))
(save-excursion (> 0 (skip-syntax-backward ".")) (bolp))
(save-excursion (> 0 (skip-syntax-backward "_()")) (bolp))
(save-excursion (and (> 0 (skip-syntax-backward "w")) (> 0 (skip-chars-backward "\s\t")) (bolp)))
(save-excursion (and (> 0 (skip-syntax-backward ".")) (> 0 (skip-chars-backward "\s\t")) (bolp)))
(save-excursion (and (> 0 (skip-syntax-backward "_()")) (> 0 (skip-chars-backward "\s\t")) (bolp)))))
(skip-chars-forward "\s\t")
(setq end (point))
(setq beg (point-at-bol))
(delete-region beg end))
;; Condition # 5
;; point = eol
;; not an empty line
;; whitespace to the left of eol
((and
(not (and (bolp) (eolp)))
(eolp)
(save-excursion (> 0 (skip-chars-backward "\s\t"))))
(setq end (point))
(skip-chars-backward "\s\t")
(setq beg (point))
(delete-region beg end))
;; Condition # 6
;; point = not eob
;; point = bolp and eolp
;; universal argument = C-u = '(4)
((and
(not (eobp))
(and (bolp) (eolp))
(equal arg '(4)))
(delete-forward-char 1))) )))

This has most likely been solved before, but instead of looking for code, we can write our own. So much fun!
This is how I would do it, hope it helps.
(defun kill-whitespace-or-word ()
(interactive)
(if (looking-at "[ \t\n]")
(let ((p (point)))
(re-search-forward "[^ \t\n]" nil :no-error)
(backward-char)
(kill-region p (point)))
(kill-word 1)))
Then bind it to a key:
(global-set-key (kbd "M-d") 'kill-whitespace-or-word)

If you are using a CC-Mode based buffer, you are probably looking for the Hungry Delete Mode minor mode.
Try C-c DEL and C-c DELETE in several places to get a feel for the difference.
If you like the way it works, you can toggle hungry deletion to work for the standard keys by doing M-x c-toggle-hungry-state or just rebind the hungry deletion functions to your preferred binding.
If you still think you need to piggyback one key to do forward kill word or whitespace, then you can do something similar to c-hungry-delete-forward, or just temporarily rebind c-delete-function and call it.
(defun c-hungry-delete-forward-word ()
"Delete the following word or all following whitespace
up to the next non-whitespace character.
See also \\[c-hungry-delete-backwards]."
(interactive)
(let ((c-delete-function (function kill-word)))
(c-hungry-delete-forward)))
Check out the Info page (ccmode) Hungry WS Deletion for more.

Related

Emacs -- debugging an overlay that is one character too wide

I'm looking for some assistance, please, debugging the second example -- it should be only one (1) character wide, but ends up being two (2) characters wide.
WORKING EXAMPLE:  The following code creates an overlay of just one (1) character wide, which is correct:
(let ((my-column (current-column)))
(overlay-put
(make-overlay
(save-excursion (beginning-of-line) (+ (point) my-column))
(+ 1 (save-excursion (beginning-of-line) (+ (point) my-column)) ) )
'face '(background-color . "pink")))
BROKEN EXAMPLE:  The following code creates an overlay that is two (2) characters wide, which is incorrect:
(let ((my-column (current-column)))
(save-excursion
(goto-char (point-min))
(while (re-search-forward "\n" nil t)
(overlay-put
(make-overlay
(save-excursion (beginning-of-line) (+ (point) my-column))
(+ 1 (save-excursion (beginning-of-line) (+ (point) my-column)) ) )
'face '(background-color . "pink"))) ))
EDIT -- FIXED EXAMPLE:  Based on the helpful guidance of #Lindydancer in the answer below, here is the revised example that now works -- perhaps it could be simplified, but at least I now understand the concept:
(let* (
(my-column (current-column))
my-line-beginning
my-line-ending
my-line-length)
(save-excursion
(goto-char (point-min))
(while (re-search-forward "\n" nil t)
(setq my-line-beginning (point))
(end-of-line)
(setq my-line-ending (point))
(setq my-line-length (- my-line-ending my-line-beginning))
(when (< my-column my-line-length)
(overlay-put
(make-overlay
(save-excursion (beginning-of-line) (+ (point) my-column))
(+ 1 (save-excursion (beginning-of-line) (+ (point) my-column)) ) )
'face '(background-color . "pink"))) )))
I think the problem is that you add the overlay to all lines. If the line is shorter than my-column, the overlay will spill over lines below, giving the impression that some overlays are two character wide, when in reality it's two one-character overlays placed next to eachother.
Try to limit the code to put the overlay only on lines that are at least my-overlay long.

How to unmark just one date in the calendar -- delete overlay

Error message: let: Wrong type argument: overlayp, (#<overlay from 478 to 480 in *Calendar*>)
mouse-1 successfully marks a date in the calendar. The built-in functions only provide for deleting all of the overlays, instead of just one particular date. Could anyone please give me a hand to delete the overlay for just one marked date -- I was thinking this could be either mouse-2 or U. I thought of using skip-syntax-forwards/backwards because some of the dates have just one digit, and some have two digits -- the cursor may be between two digits or on either side of the date. I assume we need a beginning and ending point.
(define-key calendar-mode-map [mouse-1] (lambda () (interactive)
(calendar-mark-visible-date (calendar-cursor-to-date t))))
(define-key calendar-mode-map "U" (lambda () (interactive)
(cond
((save-excursion (> 0 (skip-syntax-backward "w")))
(skip-syntax-backward "w")
(let ((beg (point)))
(skip-syntax-forward "w")
(let ((end (point)))
(delete-overlay (overlays-in beg end)))))
((save-excursion (< 0 (skip-syntax-forward "w")))
(skip-syntax-forward "w")
(let ((end (point)))
(skip-syntax-backward "w")
(let ((beg (point)))
(delete-overlay (overlays-in beg end))))))))
EDIT (December 12, 2013):  First working draft based upon the helpful answer from #Drew. Added mouse-set-point and interactive code to support said function. Consolidated keyboard shortcuts so that mouse-1 activates / deactivates the overlay at point -- now uses overlays-at.
EDIT (January 1, 2014):  To distinguish between one or more overlays at point, see the following thread: How to distinguish between different overlays at point  With the examples in that thread, different actions can occur depending upon whether a particular overlay exists at point.
(defvar lawlist-calendar-face (make-face 'lawlist-calendar-face))
(set-face-attribute 'lawlist-calendar-face nil
:background "LightCoral" :foreground "black")
(define-key calendar-mode-map [mouse-1] (lambda (event) (interactive "e")
(mouse-set-point event)
(if (not (overlays-at (point)))
(calendar-mark-visible-date (calendar-cursor-to-date t) lawlist-calendar-face)
(cond
;; cursor is one whitespace to the left of 1 to 9
((and
(save-excursion (< 0 (skip-chars-forward " \t")))
(not (save-excursion (< 0 (skip-syntax-forward "w")))))
(save-excursion
(let ((beg (point)))
(skip-chars-forward " \t")
(skip-syntax-forward "w")
(let ((end (point)))
(mapc 'delete-overlay (overlays-in beg end))))))
;; cursor is sandwiched between a digit on each side.
((and
(save-excursion (> 0 (skip-syntax-backward "w")))
(save-excursion (< 0 (skip-syntax-forward "w"))))
(save-excursion
(skip-syntax-backward "w")
(let ((beg (point)))
(skip-syntax-forward "w")
(let ((end (point)))
(mapc 'delete-overlay (overlays-in beg end))))))
;; cursor is to the far right of one or two digit dates
((and
(save-excursion (> 0 (skip-syntax-backward "w")))
(not (save-excursion (< 0 (skip-syntax-forward "w")))))
(save-excursion
(skip-syntax-backward "w")
(let ((beg (point)))
(skip-syntax-forward "w")
(let ((end (point)))
(mapc 'delete-overlay (overlays-in beg end))))))
;; cursor to the far left of one or two digits dates
((and
(save-excursion (< 0 (skip-syntax-forward "w")))
(not (save-excursion (> 0 (skip-syntax-backward "w")))))
(save-excursion
(skip-syntax-forward "w")
(let ((end (point)))
(skip-syntax-backward "w")
(let ((beg (point)))
(mapc 'delete-overlay (overlays-in beg end))))))))))
Wrt the error:
delete-overlay expects a single overlay as its argument. You are passing it a list (empty or nonempty) of overlays instead. You should iterate over the list, e.g., with while or dolist or mapc.

How to test for a syntax condition without moving the cursor?

I have been unable to come up with a method to test for a syntax condition without moving the cursor -- e.g., skip-syntax-forward and skip-chars-forward both move the cursor in order to return t or a positive value. How can I return t or nil without moving the cursor?
(defun lawlist-kill-word ()
"Mark word / symbol + whitespace to the right of the cursor, and kill same."
(interactive)
(let* (
(symbol-regexp "\\s.\\|\\s_")
(word-regexp "\\sw"))
(modify-syntax-entry ?' "_") ;; apostrophe = symbol constituent
(cond
((< 0 (skip-syntax-forward "_."))
(let ((end (point)))
(set-mark end)
(while (looking-back symbol-regexp)
(backward-char))
(let ((beg (point)))
(delete-region beg end)
(setq beg (point))
(cond
((skip-chars-forward " \t")
(setq end (point))
(set-mark end)
(delete-region beg end))))))
((< 0 (skip-syntax-forward "w"))
(let ((end (point)))
(set-mark end)
(while (looking-back word-regexp)
(backward-char))
(let ((beg (point)))
(delete-region beg end)
(setq beg (point))
(cond
((skip-chars-forward " \t")
(setq end (point))
(set-mark end)
(delete-region beg end))))))
(t
(let ((beg (point)))
(set-mark beg)
(skip-chars-forward " \t")
(let ((end (point)))
(delete-region beg end)))
(deactivate-mark)))
(modify-syntax-entry ?' "w") )) ;; apostrophe = word constituent
EDIT:  lawlist-kill-word is a work in progress -- any updates to this function will be posted to a thread related to that issue -- i.e.,: Emacs: delete whitespaces or a word
As #Tobias said: wrap cursor movements in save-excursion. Save any values you want (e.g., of (point)) in variables and return them as needed.
E.g., if you want the position four lines ahead, do something like this:
(let ((posn (save-excursion (forward-line 4) (point))))
posn)

Alternative to forward-word / backward-word to include symbols -- e.g., ***

My preferred method of indicating a location within a document that requires attention is with three asterisks ***. When it comes time to select the region containing the three asterisks using shift+right-word or shift+left-word, those functions skip over the three asterisks and move along to the next word. When I peeked inside bindings.el, I saw that left-word and right-word are adaptations of forward-word and backward-word, that are traceable to built-in functions in the C source code. Essentially, I'm looking for left-word and right-word that includes symbols such as three asterisks ***.
Can anyone please suggest a way to have a function that jumps a word or symbols to the left, and a word or symbols to the right. The function would need to behave similar to left-word and right-word so that I could select more than one word if the arrow keys are pressed more than one time -- e.g., left-word-or-symbols and right-word-or-symbols.
The following is a custom function based upon the helpful answer of below:
(defvar lawlist-movement-syntax-table
(let ((st (make-syntax-table)))
;; ` default = punctuation
;; ' default = punctuation
;; , default = punctuation
;; ; default = punctuation
(modify-syntax-entry ?{ "." st) ;; { = punctuation
(modify-syntax-entry ?} "." st) ;; } = punctuation
(modify-syntax-entry ?\" "." st) ;; " = punctuation
(modify-syntax-entry ?\\ "_" st) ;; \ = symbol
(modify-syntax-entry ?\$ "_" st) ;; $ = symbol
(modify-syntax-entry ?\% "_" st) ;; % = symbol
st)
"Syntax table used while executing custom movement functions.")
(defun lawlist-forward-entity ()
"http://stackoverflow.com/q/18675201/2112489"
(interactive "^")
(with-syntax-table lawlist-movement-syntax-table
(cond
((eolp)
(forward-char))
((and
(save-excursion (< 0 (skip-chars-forward " \t")))
(not (region-active-p)))
(skip-chars-forward " \t"))
((and
(save-excursion (< 0 (skip-chars-forward " \t")))
(region-active-p))
(skip-chars-forward " \t")
(cond
((save-excursion (< 0 (skip-syntax-forward "w")))
(skip-syntax-forward "w"))
((save-excursion (< 0 (skip-syntax-forward ".")))
(skip-syntax-forward "."))
((save-excursion (< 0 (skip-syntax-forward "_()")))
(skip-syntax-forward "_()"))))
((save-excursion (< 0 (skip-syntax-forward "w")))
(skip-syntax-forward "w")
(if (and
(not (region-active-p))
(save-excursion (< 0 (skip-chars-forward " \t"))))
(skip-chars-forward " \t")))
((save-excursion (< 0 (skip-syntax-forward ".")))
(skip-syntax-forward ".")
(if (and
(not (region-active-p))
(save-excursion (< 0 (skip-chars-forward " \t"))))
(skip-chars-forward " \t")))
((save-excursion (< 0 (skip-syntax-forward "_()")))
(skip-syntax-forward "_()")
(if (and
(not (region-active-p))
(save-excursion (< 0 (skip-chars-forward " \t"))))
(skip-chars-forward " \t"))))))
(defun lawlist-backward-entity ()
"http://stackoverflow.com/q/18675201/2112489"
(interactive "^")
(with-syntax-table lawlist-movement-syntax-table
(cond
((bolp)
(backward-char))
((save-excursion (> 0 (skip-chars-backward " \t")) (bolp))
(skip-chars-backward " \t"))
((save-excursion (> 0 (skip-chars-backward " \t")) (> 0 (skip-syntax-backward "w")))
(skip-chars-backward " \t")
(skip-syntax-backward "w"))
((save-excursion (> 0 (skip-syntax-backward "w")))
(skip-syntax-backward "w"))
((save-excursion (> 0 (skip-syntax-backward ".")))
(skip-syntax-backward "."))
((save-excursion (> 0 (skip-chars-backward " \t")) (> 0 (skip-syntax-backward ".")))
(skip-chars-backward " \t")
(skip-syntax-backward "."))
((save-excursion (> 0 (skip-syntax-backward "_()")))
(skip-syntax-backward "_()"))
((save-excursion (> 0 (skip-chars-backward " \t")) (> 0 (skip-syntax-backward "_()")))
(skip-chars-backward " \t")
(skip-syntax-backward "_()")))))
Add to the mode hook of the mode, where you are editing the text:
(modify-syntax-entry ?* "w")
Read here for more info.
Note that this will make other functions treat asterisks as if they were word constituent (though, you'd probably want it, as you'd soon discover that not only the motion command behave contrary to how you want them to).
Another way to overload only these particular commands would be to advise them with defadvice and to temporarily alter the syntax table just for the duration of the function.
You might like Emacs-24.4's superword-mode.

Is there a way to toggle a string between single and double quotes in emacs?

I'm looking for an emacs command that will toggle the surrounding quote characters on the string under the point, e.g. with the cursor in the string 'bar', hit a key and change it between:
foo = 'bar' <---> foo = "bar"
For bonus points it would:
handle toggling Python triple-quote strings (''' <---> """)
automatically change backslash escaping inside the string as appropriate.
e.g.
foo = 'bar "quote"' <---> foo = "bar \"quote\""
This could be a bit more robust:
(defun toggle-quotes ()
(interactive)
(save-excursion
(let ((start (nth 8 (syntax-ppss)))
(quote-length 0) sub kind replacement)
(goto-char start)
(setq sub (buffer-substring start (progn (forward-sexp) (point)))
kind (aref sub 0))
(while (char-equal kind (aref sub 0))
(setq sub (substring sub 1)
quote-length (1+ quote-length)))
(setq sub (substring sub 0 (- (length sub) quote-length)))
(goto-char start)
(delete-region start (+ start (* 2 quote-length) (length sub)))
(setq kind (if (char-equal kind ?\") ?\' ?\"))
(loop for i from 0
for c across sub
for slash = (char-equal c ?\\)
then (if (and (not slash) (char-equal c ?\\)) t nil) do
(unless slash
(when (member c '(?\" ?\'))
(aset sub i
(if (char-equal kind ?\") ?\' ?\")))))
(setq replacement (make-string quote-length kind))
(insert replacement sub replacement))))
It will use syntax information from the buffer to find the quotes at the beginning of the string (that is given that the strings are quoted), and will also try to flip quotes inside the string, unless they are escaped with backslash - which looks like it could be a common case.
PS. I've just realized you also wanted it to find triple quotes, so her goes.
Here's a quick hack to get you started:
(defun toggle-quotes ()
"Toggle single quoted string to double or vice versa, and
flip the internal quotes as well. Best to run on the first
character of the string."
(interactive)
(save-excursion
(re-search-backward "[\"']")
(let* ((start (point))
(old-c (char-after start))
new-c)
(setq new-c
(case old-c
(?\" "'")
(?\' "\"")))
(setq old-c (char-to-string old-c))
(delete-char 1)
(insert new-c)
(re-search-forward old-c)
(backward-char 1)
(let ((end (point)))
(delete-char 1)
(insert new-c)
(replace-string new-c old-c nil (1+ start) end)))))
The function swaps the internal quotes to the opposite, which is close to bonus 2.
Here's something even more robust, in that it doesn't delete the whole text between the quotes (doing so prevents save-excursion from keeping the point where it was, which is a pain). Also handles (un)backslash-ing nested quotes.
(defun toggle-quotes ()
(interactive)
(let* ((beg (nth 8 (syntax-ppss)))
(orig-quote (char-after beg))
(new-quote (case orig-quote
(?\' ?\")
(?\" ?\'))))
(save-restriction
(widen)
(save-excursion
(catch 'done
(unless new-quote
(message "Not inside a string")
(throw 'done nil))
(goto-char beg)
(delete-char 1)
(insert-char new-quote)
(while t
(cond ((eobp)
(throw 'done nil))
((= (char-after) orig-quote)
(delete-char 1)
(insert-char new-quote)
(throw 'done nil))
((= (char-after) ?\\)
(forward-char 1)
(when (= (char-after) orig-quote)
(delete-char -1))
(forward-char 1))
((= (char-after) new-quote)
(insert-char ?\\)
(forward-char 1))
(t (forward-char 1)))))))))
Here's a function I made for JavaScript, might help?
function swap_str(e, r, t) {
return e = e.split(r).join("WHAK_a_SWAP"), e = e.split(t).join("WHAK_b_SWAP"), e = e.split("WHAK_a_SWAP").join(t),
e = e.split("WHAK_b_SWAP").join(r);
}
//test 1
var str = 'this is "test" of a \'test\' of swapping strings';
var manipulated = swap_str(str,"'",'"');
document.writeln(manipulated)
//test 2
manipulated = swap_str(manipulated,"'",'"');
document.writeln('<hr>'+manipulated)