9

When I'm in the middle of a looooong string, like the following

(setq Emacs-beta "Which keyboard shortcut to use for navigating out of a string")

Can I skip out of it, just before the first " (after Emacs-beta) with a shortcut?

Dieter.Wilhelm
  • 1,836
  • 14
  • 25

4 Answers4

12

Emacs 25 or newer

You want C-M-u which runs the command backward-up-list.

Move backward out of one level of parentheses. This command will also work on other parentheses-like expressions defined by the current language mode.

If ESCAPE-STRINGS is non-nil (as it is interactively), move out of enclosing strings as well.

nanny
  • 5,704
  • 18
  • 38
3

It's doable like this:

(defun exit-string-backwards ()
  (interactive)
  (goto-char (nth 8 (syntax-ppss))))

In lispy-mode, pressing [ will bring you to the start of the containing list, exiting any string. Once you're at the start of the list, you can reach other parts easily, for example, to get to the end of Emacs-beta press 2m.

abo-abo
  • 13,943
  • 1
  • 29
  • 43
1

This is actually a very interesting Emacs Lisp programming question. We can keep forward-char or backward-char until we found the character under cursor is not part of the string.

You can use the font face to decide whether the character is inside the string. This is a great trick I learned from flyspell. In theory it's not perfect, but in real world it works with almost any programming language's major modes on Emacs23, Emacs24, Emacs25.

Here is the complete code:

(defun font-face-is-similar (f1 f2)
  (let (rlt)
    ;; (message "f1=%s f2=%s" f1 f2)
    ;; in emacs-lisp-mode, the '^' from "^abde" has list of faces:
    ;;   (font-lock-negation-char-face font-lock-string-face)
    (if (listp f1) (setq f1 (nth 1 f1)))
    (if (listp f2) (setq f2 (nth 1 f2)))

    (if (eq f1 f2) (setq rlt t)
      ;; C++ comment has different font face for limit and content
      ;; f1 or f2 could be a function object because of rainbow mode
      (if (and (string-match "-comment-" (format "%s" f1)) (string-match "-comment-" (format "%s" f2)))
          (setq rlt t)))
    rlt))

(defun goto-edge-by-comparing-font-face (&optional step)
"Goto either the begin or end of string/comment/whatever.
If step is -1, go backward."
  (interactive "P")
  (let ((cf (get-text-property (point) 'face))
        (p (point))
        rlt
        found
        end)
    (unless step (setq step 1)) ;default value
    (setq end (if (> step 0) (point-max) (point-min)))
    (while (and (not found) (not (= end p)))
      (if (not (font-face-is-similar (get-text-property p 'face) cf))
          (setq found t)
        (setq p (+ p step))))
    (if found (setq rlt (- p step))
      (setq rlt p))
    ;; (message "rlt=%s found=%s" rlt found)
    (goto-char rlt)))

Usage:

  • (goto-edge-by-comparing-font-face 1) go to the right edge, (goto-edge-by-comparing-font-face -1) go to the left edge
  • it's not dependent on any specific major-mode, you can use it anywhere in any language

backward-up-list is not very reliable from user's point of view because it's designed as a generic command based on scan-sexps. For example, for code if (true) { return 'hello world'; } in js2-mode, it will move the focus to the { character instead of the first single quote character. For code printf("hello world") in c++-mode, it won't work. I tested with Emacs 24.5

chen bin
  • 4,781
  • 18
  • 36
  • This goes to the right edge, the OP has asked to the point before the left edge. What if the lisp-mode is not activated? – Name Jul 26 '15 at 04:38
  • (goto-edge-by-comparing-font-face -1) go to the left edge, `backward-char` or `forward-char` if you move the cursor out side of the edge. – chen bin Jul 26 '15 at 07:08
  • Ok, nice idea, but if you run it when text-mode is activated, the points goes after `)`. So depending on the modes, your code has different behaviors. – Name Jul 26 '15 at 07:17
  • It use the same algorithm as flyspell. So it support all the major modes flyspell support – chen bin Jul 26 '15 at 23:52
0

Here's one more way (which I tend to use more) just for variety. The solution is not specific to strings but it works for this use case too.

  • Install iy-go-to-char (also available on Melpa).

  • Bind iy-go-to-char-backward and iy-go-to-char to your preferred bindings. For the sake of this explanation, let's say you bound iy-go-to-char-backward to C-c C-,and iy-go-to-char to C-c C-..

  • Now if you are inside the string, you would call iy-go-to-char-backward and type ".

    That would look like C-c C-, ". If you have iy-go-to-char-continue-when-repeating set to t (default), pressing " once again will take you to a " char occurrence before that, and so on.

  • If you are inside the string, and if you now want to go to the end of the string, you would call iy-go-to-char and type ".

    That would look like C-c C-. ". If you have iy-go-to-char-continue-when-repeating set to t (default), pressing " once again will take you to the next " char occurrence.

Kaushal Modi
  • 25,203
  • 3
  • 74
  • 179