"
10 | And I place the cursor between " " and "id"
11 | And I press "C-@"
12 | And I press "C-@"
13 | And I press "C-@"
14 | Then the region should be "id="5""
15 |
16 | Scenario: Mark html attribute from end
17 | Given I turn on html-mode
18 | And there is no region selected
19 | When I insert "
"
20 | And I go to point "12"
21 | And I press "C-@"
22 | And I press "C-@"
23 | Then the region should be "id="5""
24 |
25 | Scenario: Mark html tags, part 1
26 | Given I turn on html-mode
27 | And there is no region selected
28 | When I insert "...
..."
29 | And I place the cursor between "before " and "
"
30 | And I press "C-@"
31 | Then the region should be ""
32 |
33 | Scenario: Mark html tags, part 2
34 | Given I turn on html-mode
35 | And there is no region selected
36 | When I insert "... ..."
37 | And I place the cursor between "before " and ""
38 | And I press "C-@"
39 | And I press "C-@"
40 | Then the region should be ""
41 |
42 | Scenario: Mark html tags, part 3
43 | Given I turn on html-mode
44 | And there is no region selected
45 | When I insert "... ..."
46 | And I place the cursor between "before " and ""
47 | And I press "C-@"
48 | And I press "C-@"
49 | And I press "C-@"
50 | Then the region should be "before "
51 |
52 | Scenario: Mark html tags, part 4
53 | Given I turn on html-mode
54 | And there is no region selected
55 | When I insert "... ..."
56 | And I place the cursor between "before " and ""
57 | And I press "C-@"
58 | And I press "C-@"
59 | And I press "C-@"
60 | And I press "C-@"
61 | Then the region should be "before
"
62 |
63 | Scenario: Mark html tags, part 5
64 | Given I turn on html-mode
65 | And there is no region selected
66 | When I insert "... ..."
67 | And I place the cursor between "before " and ""
68 | And I press "C-@"
69 | And I press "C-@"
70 | And I press "C-@"
71 | And I press "C-@"
72 | And I press "C-@"
73 | Then the region should be "before
after"
74 |
75 | Scenario: Mark html tags, part 6
76 | Given I turn on html-mode
77 | And there is no region selected
78 | When I insert "... ..."
79 | And I place the cursor between "before " and ""
80 | And I press "C-@"
81 | And I press "C-@"
82 | And I press "C-@"
83 | And I press "C-@"
84 | And I press "C-@"
85 | And I press "C-@"
86 | Then the region should be ""
87 |
88 | Scenario: Text mode expansions shouldn't be here
89 | Given I turn on html-mode
90 | And there is no region selected
91 | When I insert "Sentence the first. Sentence the second"
92 | And I place the cursor between "first. " and "Sentence"
93 | And I press "C-@"
94 | And I press "C-@"
95 | Then the region should be "Sentence the first. Sentence the second"
96 |
--------------------------------------------------------------------------------
/html-mode-expansions.el:
--------------------------------------------------------------------------------
1 | ;;; html-mode-expansions.el --- HTML-specific expansions for expand-region -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2011-2023 Free Software Foundation, Inc
4 |
5 | ;; Author: Magnar Sveen
6 | ;; Keywords: marking region
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 |
23 | ;; Extra expansions for HTML that I've found useful so far:
24 | ;;
25 | ;; er/mark-html-attribute
26 | ;; er/mark-inner-tag
27 | ;; er/mark-outer-tag
28 | ;;
29 | ;; Feel free to contribute any other expansions for HTML at
30 | ;;
31 | ;; https://github.com/magnars/expand-region.el
32 |
33 | ;;; Code:
34 |
35 | (require 'expand-region-core)
36 | (require 'sgml-mode)
37 |
38 | (defun er/mark-html-attribute ()
39 | "Mark html-attribute.
40 | Presumes that point is at the assignment part of attr=\"value\".
41 | If point is inside the value-string, the quotes will be marked
42 | first anyway. Does not support html-attributes with spaces
43 | around the equal sign or unquoted attributes atm."
44 | (interactive)
45 | (when (or (looking-at "\\(\\s_\\|\\sw\\)*=")
46 | (er/looking-back-exact "="))
47 | (search-backward " ")
48 | (forward-char 1)
49 | (set-mark (point))
50 | (search-forward "=")
51 | (forward-sexp 1)
52 | (exchange-point-and-mark)))
53 |
54 | (defun er--looking-at-marked-tag ()
55 | "Is point looking at a tag that is entirely marked?"
56 | (and (looking-at "<")
57 | (>= (mark)
58 | (save-excursion
59 | (sgml-skip-tag-forward 1)
60 | (point)))))
61 |
62 | (defun er--inside-tag-p ()
63 | "Is point inside a tag?"
64 | (save-excursion
65 | (not (null (sgml-get-context)))))
66 |
67 | (defun er/mark-outer-tag ()
68 | "Mark from opening to closing tag, including the tags."
69 | (interactive)
70 | (when (and (er--inside-tag-p)
71 | (or (not (looking-at "<"))
72 | (er--looking-at-marked-tag)))
73 | (goto-char (aref (car (last (sgml-get-context))) 2)))
74 | (when (looking-at "<")
75 | (set-mark (point))
76 | (sgml-skip-tag-forward 1)
77 | (exchange-point-and-mark)))
78 |
79 | (defun er/mark-inner-tag ()
80 | "Mark the contents of an open tag, not including the tags."
81 | (interactive)
82 | (goto-char (aref (car (last (sgml-get-context))) 3))
83 | (set-mark (point))
84 | (backward-char 1)
85 | (sgml-skip-tag-forward 1)
86 | (search-backward "")
87 | (exchange-point-and-mark))
88 |
89 | (defun er/add-html-mode-expansions ()
90 | "Adds HTML-specific expansions for buffers in html-mode"
91 | (set (make-local-variable 'er/try-expand-list) (append
92 | er/try-expand-list
93 | '(er/mark-html-attribute
94 | er/mark-inner-tag
95 | er/mark-outer-tag))))
96 |
97 | (er/enable-mode-expansions 'html-mode #'er/add-html-mode-expansions)
98 | (er/enable-mode-expansions 'rhtml-mode #'er/add-html-mode-expansions)
99 | (er/enable-mode-expansions 'nxhtml-mode #'er/add-html-mode-expansions)
100 | (er/enable-mode-expansions 'web-mode #'er/add-html-mode-expansions)
101 |
102 | (provide 'html-mode-expansions)
103 |
104 | ;; html-mode-expansions.el ends here
105 |
--------------------------------------------------------------------------------
/latex-mode-expansions.el:
--------------------------------------------------------------------------------
1 | ;;; latex-mode-expansions.el --- LaTeX-specific expansions for expand-region -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc
4 |
5 | ;; Author: Ivan Andrus
6 | ;; Based on js-mode-expansions by: Magnar Sveen
7 | ;; Keywords: marking region
8 |
9 | ;; This program is free software; you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; This program is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with this program. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;; This is for AUCTeX, not the builtin latex-mode.
25 |
26 | ;; Feel free to contribute any other expansions for LaTeX at
27 | ;;
28 | ;; https://github.com/magnars/expand-region.el
29 |
30 | ;;; Code:
31 |
32 | (require 'expand-region-core)
33 | ;referenced free variables and functions defined in mode
34 | (defvar texmathp-why)
35 | (defvar texmathp-tex-commands1)
36 | (defvar texmathp-onoff-regexp)
37 | (defvar LaTeX-mode-hook)
38 | (declare-function LaTeX-mark-environment "latex")
39 | (declare-function texmathp "texmathp")
40 |
41 | (defun er/mark-LaTeX-inside-environment ()
42 | "Like `LaTeX-mark-environment' but marks the inside of the environment.
43 | Skips past [] and {} arguments to the environment."
44 | (interactive)
45 | (LaTeX-mark-environment)
46 | (when (looking-at "\\\\begin{")
47 | (forward-sexp 2)
48 | ;; Assume these are arguments
49 | (while (looking-at "[ \t\n]*[{[]")
50 | (forward-sexp 1))
51 | ;; Go to next line if there is nothing interesting on this one
52 | (skip-syntax-forward " ") ;; newlines are ">" i.e. end comment
53 | (when (looking-at "%\\|$")
54 | (forward-line))
55 | ;; Clean up the end portion
56 | (exchange-point-and-mark)
57 | (backward-sexp 2)
58 | (skip-syntax-backward " ")
59 | (exchange-point-and-mark)))
60 |
61 | (defun er/mark-LaTeX-math ()
62 | "Mark current math environment."
63 | (interactive)
64 | (when (texmathp)
65 | (let* ((string (car texmathp-why))
66 | (pos (cdr texmathp-why))
67 | (reason (assoc string texmathp-tex-commands1))
68 | (type (cadr reason)))
69 | (cond
70 | ((eq type 'env-on) ;; environments equation, align, etc.
71 | (er/mark-LaTeX-inside-environment))
72 | ((eq type 'arg-on) ;; \ensuremath etc.
73 | (goto-char pos)
74 | (set-mark (point))
75 | (forward-sexp 2)
76 | (exchange-point-and-mark))
77 | ((eq type 'sw-toggle) ;; $ and $$
78 | (goto-char pos)
79 | (set-mark (point))
80 | (forward-sexp 1)
81 | (exchange-point-and-mark))
82 | ((eq type 'sw-on) ;; \( and \[
83 | (re-search-forward texmathp-onoff-regexp)
84 | (set-mark pos)
85 | (exchange-point-and-mark))
86 | (t (error (format "Unknown reason to be in math mode: %s" type)))))))
87 |
88 | (defun er/add-latex-mode-expansions ()
89 | "Adds expansions for buffers in latex-mode"
90 | (set (make-local-variable 'er/try-expand-list)
91 | (append
92 | er/try-expand-list
93 | '(LaTeX-mark-environment
94 | LaTeX-mark-section
95 | er/mark-LaTeX-inside-environment
96 | er/mark-LaTeX-math))))
97 |
98 | (let ((latex-mode-hook LaTeX-mode-hook))
99 | (er/enable-mode-expansions 'latex-mode #'er/add-latex-mode-expansions)
100 | (setq LaTeX-mode-hook latex-mode-hook))
101 |
102 | (provide 'latex-mode-expansions)
103 |
104 | ;; latex-mode-expansions.el ends here
105 |
--------------------------------------------------------------------------------
/features/c-mode-expansions.feature:
--------------------------------------------------------------------------------
1 | Feature: C-mode expansions
2 | Background:
3 | Given there is no region selected
4 | And I turn on c-mode
5 | And I insert:
6 | """
7 | int main (int argc, char **argv) {
8 | int x = 0;
9 | double y = 1.;
10 | float z = my_function (x, y);
11 | char t = argv [x + 3];
12 |
13 | fun ( (char*)bob, joe );
14 |
15 | int i = 0;
16 | for ( ; i.
21 |
22 | ;;; Commentary:
23 |
24 | ;; The file needs to be weirdly name (prefixed with the-) to avoid
25 | ;; conflict with org-reload, which bases its functionality on the names
26 | ;; of files, for some reason.
27 | ;;
28 | ;; Feel free to contribute any other expansions for org-mode at
29 | ;;
30 | ;; https://github.com/magnars/expand-region.el
31 |
32 | ;;; Code:
33 |
34 | (require 'expand-region-core)
35 | (require 'er-basic-expansions)
36 | (require 'org-macs)
37 | (require 'org-element)
38 |
39 | (declare-function org-up-element "org")
40 | (declare-function org-mark-subtree "org")
41 |
42 | (defun er/mark-org-element ()
43 | (interactive)
44 | (let* ((el (org-element-at-point))
45 | (begin (plist-get (cadr el) :begin))
46 | (end (plist-get (cadr el) :end)))
47 | (goto-char begin)
48 | (set-mark (point))
49 | (goto-char end)
50 | (exchange-point-and-mark)))
51 |
52 | (defun er/mark-org-element-parent ()
53 | (interactive)
54 | (let* ((el (plist-get (cadr (org-element-at-point)) :parent))
55 | (begin (plist-get (cadr el) :begin))
56 | (end (plist-get (cadr el) :end)))
57 | (when (and begin end)
58 | (goto-char begin)
59 | (set-mark (point))
60 | (goto-char end)
61 | (exchange-point-and-mark))))
62 |
63 | (defun er/mark-sentence ()
64 | "Marks one sentence."
65 | (interactive)
66 | (forward-char 1)
67 | (backward-sentence 1)
68 | (set-mark (point))
69 | (forward-sentence 1)
70 | (exchange-point-and-mark))
71 |
72 | (defun er/mark-paragraph ()
73 | "Marks one paragraph."
74 | (interactive)
75 | (mark-paragraph)
76 | (exchange-point-and-mark)
77 | (skip-chars-backward er--space-str)
78 | (exchange-point-and-mark)
79 | (skip-chars-forward er--space-str))
80 |
81 | (defun er/mark-org-code-block ()
82 | "Marks an org-code-block."
83 | (interactive)
84 | (let ((case-fold-search t)
85 | (re "#\\+begin_\\(\\sw+\\)"))
86 | (unless (looking-at re)
87 | (search-backward-regexp re))
88 | (set-mark (point))
89 | (search-forward (concat "#+end_" (match-string 1)))
90 | (exchange-point-and-mark)))
91 |
92 | (defun er/mark-org-parent ()
93 | "Marks a heading 1 level up from current subheading"
94 | (interactive)
95 | (org-up-element)
96 | (org-mark-subtree))
97 |
98 | (defun er/save-org-mode-excursion (action)
99 | "Save outline visibility while expanding in org-mode"
100 | (org-save-outline-visibility t
101 | (funcall action)))
102 |
103 | (defun er/add-org-mode-expansions ()
104 | "Adds org-specific expansions for buffers in org-mode"
105 | (set (make-local-variable 'er/try-expand-list)
106 | (append
107 | (remove #'er/mark-defun er/try-expand-list)
108 | '(org-mark-subtree
109 | er/mark-org-element
110 | er/mark-org-element-parent
111 | er/mark-org-code-block
112 | er/mark-sentence
113 | er/mark-org-parent
114 | er/mark-paragraph)))
115 | (set (make-local-variable 'er/save-mode-excursion)
116 | #'er/save-org-mode-excursion))
117 |
118 | (er/enable-mode-expansions 'org-mode #'er/add-org-mode-expansions)
119 |
120 | (provide 'the-org-mode-expansions)
121 |
--------------------------------------------------------------------------------
/clojure-mode-expansions.el:
--------------------------------------------------------------------------------
1 | ;;; clojure-mode-expansions.el --- Clojure-specific expansions for expand-region -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2011-2023 Free Software Foundation, Inc
4 |
5 | ;; Author: Magnar Sveen
6 | ;; Keywords: marking region
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 |
23 | ;; Extra expansions for clojure-mode:
24 | ;;
25 | ;; * `er/mark-clj-word` - includes dashes, but not slashes.
26 | ;; * `er/mark-clj-regexp-literal`
27 | ;; * `er/mark-clj-function-literal`
28 | ;;
29 | ;; Feel free to contribute any other expansions for Clojure at
30 | ;;
31 | ;; https://github.com/magnars/expand-region.el
32 |
33 | ;;; Code:
34 |
35 | (require 'expand-region-core)
36 | (require 'er-basic-expansions)
37 |
38 | (defun er/mark-clj-word ()
39 | "Mark the entire word around or in front of point, including dashes."
40 | (interactive)
41 | (let ((word-regexp "\\(\\sw\\|-\\)"))
42 | (when (or (looking-at word-regexp)
43 | (er/looking-back-on-line word-regexp))
44 | (while (looking-at word-regexp)
45 | (forward-char))
46 | (set-mark (point))
47 | (while (er/looking-back-on-line word-regexp)
48 | (backward-char)))))
49 |
50 | (defun er/mark-clj-set-literal ()
51 | "Mark clj-set-literal presumes that point is outside the brackets.
52 | If point is inside the brackets, those will be marked first anyway."
53 | (interactive)
54 | (when (or (looking-at "#{")
55 | (er/looking-back-exact "#"))
56 | (forward-char 1)
57 | (search-backward "#")
58 | (set-mark (point))
59 | (search-forward "{")
60 | (forward-char -1)
61 | (forward-list 1)
62 | (exchange-point-and-mark)))
63 |
64 | (defun er/mark-clj-regexp-literal ()
65 | "Mark clj-regexp-literal presumes that point is outside the string.
66 | If point is inside the string, the quotes will be marked first anyway."
67 | (interactive)
68 | (when (or (looking-at "#\"")
69 | (er/looking-back-exact "#"))
70 | (forward-char 1)
71 | (search-backward "#")
72 | (set-mark (point))
73 | (search-forward "\"")
74 | (forward-char 1)
75 | (er--move-point-forward-out-of-string)
76 | (exchange-point-and-mark)))
77 |
78 | (defun er/mark-clj-function-literal ()
79 | "Mark clj-function-literal presumes that point is outside the parens.
80 | If point is inside the parens, they will be marked first anyway."
81 | (interactive)
82 | (when (or (looking-at "#(")
83 | (er/looking-back-exact "#"))
84 | (forward-char)
85 | (search-backward "#")
86 | (set-mark (point))
87 | (search-forward "(")
88 | (backward-char)
89 | (forward-list)
90 | (exchange-point-and-mark)))
91 |
92 | (defun er/add-clojure-mode-expansions ()
93 | "Adds clojure-specific expansions for buffers in clojure-mode"
94 | (set (make-local-variable 'er/try-expand-list) (append
95 | er/try-expand-list
96 | '(er/mark-clj-word
97 | er/mark-clj-regexp-literal
98 | er/mark-clj-set-literal
99 | er/mark-clj-function-literal))))
100 |
101 | (er/enable-mode-expansions 'clojure-mode #'er/add-clojure-mode-expansions)
102 | (er/enable-mode-expansions 'nrepl-mode #'er/add-clojure-mode-expansions)
103 |
104 | (provide 'clojure-mode-expansions)
105 |
106 | ;; clojure-mode-expansions.el ends here
107 |
--------------------------------------------------------------------------------
/features/nxml-mode-expansions.feature:
--------------------------------------------------------------------------------
1 | Feature: nxml-mode expansions
2 | In order to quickly and precisely mark xml units
3 | As an Emacs user
4 | I want to expand to them
5 |
6 | Scenario: Mark xml attribute inside quotes
7 | Given I turn on nxml-mode
8 | And there is no region selected
9 | When I insert ""
10 | And I place the cursor after "my"
11 | And I press "C-@"
12 | Then the region should be "myAttr"
13 |
14 | Scenario: Mark xml attribute with quotes
15 | Given I turn on nxml-mode
16 | And there is no region selected
17 | When I insert ""
18 | And I place the cursor after "my"
19 | And I press "C-@"
20 | And I press "C-@"
21 | Then the region should be ""myAttr""
22 |
23 | Scenario: Mark xml attribute with xpath inside quotes
24 | Given I turn on nxml-mode
25 | And there is no region selected
26 | When I insert ""
27 | And I place the cursor after "a/"
28 | And I press "C-@"
29 | And I press "C-@"
30 | Then the region should be "a/b/c"
31 |
32 | Scenario: Mark xml attribute with xpath inside quotes
33 | Given I turn on nxml-mode
34 | And there is no region selected
35 | When I insert ""
36 | And I place the cursor after "a/"
37 | And I press "C-@"
38 | And I press "C-@"
39 | And I press "C-@"
40 | Then the region should be ""a/b/c""
41 |
42 | Scenario: Mark xml attribute from start
43 | Given I turn on nxml-mode
44 | And there is no region selected
45 | When I insert ""
46 | And I place the cursor between " " and "id"
47 | And I press "C-@"
48 | And I press "C-@"
49 | Then the region should be "id="5""
50 |
51 | Scenario: Mark xml tags, part 1
52 | Given I turn on nxml-mode
53 | And there is no region selected
54 | When I insert "...
..."
55 | And I place the cursor between "before " and "
"
56 | And I press "C-@"
57 | Then the region should be ""
58 |
59 | Scenario: Mark xml tags, part 2
60 | Given I turn on nxml-mode
61 | And there is no region selected
62 | When I insert "... ..."
63 | And I place the cursor between "before " and ""
64 | And I press "C-@"
65 | And I press "C-@"
66 | Then the region should be ""
67 |
68 | Scenario: Mark xml tags, part 3
69 | Given I turn on nxml-mode
70 | And there is no region selected
71 | When I insert "... ..."
72 | And I place the cursor between "before " and ""
73 | And I press "C-@"
74 | And I press "C-@"
75 | And I press "C-@"
76 | Then the region should be "before "
77 |
78 | Scenario: Mark xml tags, part 4
79 | Given I turn on nxml-mode
80 | And there is no region selected
81 | When I insert "... ..."
82 | And I place the cursor between "before " and ""
83 | And I press "C-@"
84 | And I press "C-@"
85 | And I press "C-@"
86 | And I press "C-@"
87 | Then the region should be "before
"
88 |
89 | Scenario: Mark xml tags, part 5
90 | Given I turn on nxml-mode
91 | And there is no region selected
92 | When I insert "... ..."
93 | And I place the cursor between "before " and ""
94 | And I press "C-@"
95 | And I press "C-@"
96 | And I press "C-@"
97 | And I press "C-@"
98 | And I press "C-@"
99 | Then the region should be "before
after"
100 |
101 | Scenario: Mark xml tags, part 6
102 | Given I turn on nxml-mode
103 | And there is no region selected
104 | When I insert "... ..."
105 | And I place the cursor between "before " and ""
106 | And I press "C-@"
107 | And I press "C-@"
108 | And I press "C-@"
109 | And I press "C-@"
110 | And I press "C-@"
111 | And I press "C-@"
112 | Then the region should be ""
113 |
--------------------------------------------------------------------------------
/expand-region-custom.el:
--------------------------------------------------------------------------------
1 | ;;; expand-region-custom.el --- Increase selected region by semantic units. -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc
4 |
5 | ;; Author: Magnar Sveen
6 | ;; Keywords: marking region
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 |
23 | ;; This file holds customization variables.
24 |
25 | ;;; Code:
26 |
27 | ;;;###autoload
28 | (defgroup expand-region nil
29 | "Increase selected region by semantic units."
30 | :group 'tools)
31 |
32 | ;;;###autoload
33 | (defcustom expand-region-preferred-python-mode 'python
34 | "The name of your preferred python mode."
35 | :type '(choice (const :tag "Emacs' python.el" python)
36 | (const :tag "fgallina's python.el" fgallina-python)
37 | (const :tag "python-mode.el" python-mode)))
38 |
39 | ;;;###autoload
40 | (defcustom expand-region-guess-python-mode t
41 | "If expand-region should attempt to guess your preferred python mode."
42 | :type '(choice (const :tag "Guess" t)
43 | (const :tag "Do not guess" nil)))
44 |
45 | (defun expand-region-guess-python-mode ()
46 | "Guess the user's preferred python mode."
47 | (setq expand-region-preferred-python-mode
48 | (if (fboundp 'python-setup-brm)
49 | 'python
50 | 'fgallina-python)))
51 |
52 | ;;;###autoload
53 | (defcustom expand-region-autocopy-register ""
54 | "Register to copy most recent expand or contract to.
55 |
56 | Activated when set to a string of a single character (for example, \"e\")."
57 | :type 'string)
58 |
59 | ;;;###autoload
60 | (defcustom expand-region-skip-whitespace t
61 | "If expand-region should skip past whitespace on initial expansion."
62 | :type '(choice (const :tag "Skip whitespace" t)
63 | (const :tag "Do not skip whitespace" nil)))
64 |
65 | ;;;###autoload
66 | (defcustom expand-region-fast-keys-enabled t
67 | "If expand-region should bind fast keys after initial expand/contract."
68 | :type '(choice (const :tag "Enable fast keys" t)
69 | (const :tag "Disable fast keys" nil)))
70 |
71 | ;;;###autoload
72 | (defcustom expand-region-contract-fast-key "-"
73 | "Key to use after an initial expand/contract to contract once more."
74 | :type 'string)
75 |
76 | ;;;###autoload
77 | (defcustom expand-region-reset-fast-key "0"
78 | "Key to use after an initial expand/contract to undo."
79 | :type 'string)
80 |
81 | ;;;###autoload
82 | (defcustom expand-region-exclude-text-mode-expansions
83 | '(html-mode nxml-mode)
84 | "List of modes derived from `text-mode' to exclude from text mode expansions."
85 | :type '(repeat (symbol :tag "Major Mode" unknown)))
86 |
87 | ;;;###autoload
88 | (defcustom expand-region-smart-cursor nil
89 | "Defines whether the cursor should be placed intelligently after expansion.
90 |
91 | If set to t, and the cursor is already at the beginning of the new region,
92 | keep it there; otherwise, put it at the end of the region.
93 |
94 | If set to nil, always place the cursor at the beginning of the region."
95 | :type '(choice (const :tag "Smart behaviour" t)
96 | (const :tag "Standard behaviour" nil)))
97 |
98 | ;;;###autoload
99 | (define-obsolete-variable-alias 'er/enable-subword-mode?
100 | 'expand-region-subword-enabled "2019-03-23")
101 |
102 | ;;;###autoload
103 | (defcustom expand-region-subword-enabled nil
104 | "Whether expand-region should use subword expansions."
105 | :type '(choice (const :tag "Enable subword expansions" t)
106 | (const :tag "Disable subword expansions" nil)))
107 |
108 | (defcustom expand-region-show-usage-message t
109 | "Whether expand-region should show usage message."
110 | :group 'expand-region
111 | :type 'boolean)
112 |
113 | (provide 'expand-region-custom)
114 |
115 | ;;; expand-region-custom.el ends here
116 |
--------------------------------------------------------------------------------
/nxml-mode-expansions.el:
--------------------------------------------------------------------------------
1 | ;;; nxml-mode-expansions.el --- Nxml-specific expansions for expand-region -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc
4 |
5 | ;; Author: Ivan Andrus
6 | ;; Based on js-mode-expansions by: Magnar Sveen
7 | ;; Keywords: marking region
8 |
9 | ;; This program is free software; you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; This program is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with this program. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;; Feel free to contribute any other expansions for Nxml at
25 | ;;
26 | ;; https://github.com/magnars/expand-region.el
27 |
28 | ;;; Code:
29 |
30 | (require 'cl-lib)
31 | (require 'expand-region-core)
32 | (require 'html-mode-expansions)
33 | (require 'nxml-mode)
34 |
35 | (defun er/mark-nxml-tag ()
36 | "Marks one nxml element e.g. "
37 | (interactive)
38 | (cond ((looking-at "<")
39 | (nxml-mark-token-after))
40 | ((er/looking-back-exact ">")
41 | (backward-char 1)
42 | (nxml-mark-token-after))
43 | ((er/looking-back-max "<[^<>]*" 1000)
44 | (nxml-mark-token-after))))
45 |
46 | (defun er/mark-nxml-element ()
47 | "Marks one nxml element e.g.
...
"
48 | (interactive)
49 | (if (not (looking-at "<[^/]"))
50 | (er/mark-nxml-containing-element)
51 | (set-mark (point))
52 | (nxml-forward-element)
53 | (exchange-point-and-mark)))
54 |
55 | (defun er/mark-nxml-containing-element ()
56 | "Marks one nxml element, but always e.g. ...
"
57 | (interactive)
58 | (nxml-up-element)
59 | (set-mark (point))
60 | (nxml-backward-element))
61 |
62 | (defun er/mark-nxml-inside-element ()
63 | "Marks the inside Nxml statement, eg. ...
"
64 | (interactive)
65 | (let ((nxml-sexp-element-flag nil))
66 | (nxml-up-element)
67 | (nxml-forward-balanced-item -1)
68 | (set-mark (point))
69 | (nxml-backward-up-element)
70 | (nxml-forward-balanced-item 1)))
71 |
72 | (defun er/inside-nxml-attribute-string? ()
73 | "Returns the attribute from `xmltok-attributes' array that
74 | point is in, or otherwise nil"
75 | (save-excursion
76 | (forward-char 1)
77 | (nxml-token-before))
78 | (cl-find-if (lambda (att)
79 | (and (<= (xmltok-attribute-value-start att) (point))
80 | (>= (xmltok-attribute-value-end att) (point))))
81 | xmltok-attributes))
82 |
83 | (defun er/mark-nxml-attribute-inner-string ()
84 | "Marks an attribute string"
85 | (interactive)
86 | (let ((attr (er/inside-nxml-attribute-string?)))
87 | (when attr
88 | (set-mark (xmltok-attribute-value-start attr))
89 | (goto-char (xmltok-attribute-value-end attr))
90 | (exchange-point-and-mark))))
91 |
92 | (defun er/mark-nxml-attribute-string ()
93 | "Marks an attribute string inside quotes."
94 | (interactive)
95 | (let ((attr (er/inside-nxml-attribute-string?)))
96 | (when attr
97 | (set-mark (1- (xmltok-attribute-value-start attr)))
98 | (goto-char (1+ (xmltok-attribute-value-end attr)))
99 | (exchange-point-and-mark))))
100 |
101 | (defun er/add-nxml-mode-expansions ()
102 | "Adds Nxml-specific expansions for buffers in nxml-mode"
103 | (interactive)
104 | (set (make-local-variable 'er/try-expand-list)
105 | (append
106 | '(nxml-mark-paragraph
107 | ;; nxml-mark-token-after ;; Marks the current tag, etc. It's a bit schizophrenic
108 | er/mark-nxml-tag
109 | er/mark-nxml-inside-element
110 | er/mark-nxml-element
111 | er/mark-nxml-containing-element
112 | er/mark-nxml-attribute-string
113 | er/mark-nxml-attribute-inner-string
114 | ;; Steal from html-mode-expansions
115 | er/mark-html-attribute)
116 | ;; some normal marks are more hindrance than help:
117 | (remove 'er/mark-method-call
118 | (remove 'er/mark-symbol-with-prefix
119 | (remove 'er/mark-symbol er/try-expand-list))))))
120 |
121 | (er/enable-mode-expansions 'nxml-mode #'er/add-nxml-mode-expansions)
122 |
123 | (provide 'nxml-mode-expansions)
124 |
125 | ;; nxml-mode-expansions.el ends here
126 |
--------------------------------------------------------------------------------
/python-mode-expansions.el:
--------------------------------------------------------------------------------
1 | ;;; python-mode-expansions.el --- python-mode-specific expansions for expand-region -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc
4 |
5 | ;; Author: Felix Geller
6 | ;; Based on python-mode-expansions by: Ivan Andrus
7 | ;; Keywords: marking region python
8 |
9 | ;; This program is free software; you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; This program is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with this program. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 | ;; Commentary:
25 | ;; cf. https://github.com/magnars/expand-region.el/pull/18
26 |
27 | ;; For python-mode: https://launchpad.net/python-mode
28 | ;; - Mark functionality taken from python-mode:
29 | ;; - `py-mark-expression'
30 | ;; - `py-mark-statement'
31 | ;; - `py-mark-block'
32 | ;; - `py-mark-class'
33 | ;; - Additions implemented here:
34 | ;; - `er/mark-inside-python-string'
35 | ;; - `er/mark-outside-python-string'
36 | ;; - `er/mark-outer-python-block'
37 | ;; - Supports multi-line strings
38 | ;; - Supports incremental expansion of nested blocks
39 |
40 | ;;; Code:
41 |
42 | (require 'expand-region-core)
43 |
44 | (defvar er--python-string-delimiter "'\"")
45 |
46 | (defalias 'py-goto-beyond-clause #'py-end-of-clause-bol)
47 |
48 | (declare-function py-in-string-p "python-mode")
49 | (declare-function py-beginning-of-block "python-mode")
50 | (declare-function py-end-of-block "python-mode")
51 | (declare-function py-mark-block-or-clause "python-mode")
52 | (declare-function py-end-of-clause-bol "python-mode")
53 | (defvar py-indent-offset)
54 |
55 | (defun er/mark-outside-python-string ()
56 | "Marks region outside a (possibly multi-line) Python string"
57 | (interactive)
58 | (let ((string-beginning (py-in-string-p)))
59 | (when string-beginning
60 | (goto-char string-beginning)
61 | (set-mark (point))
62 | (forward-sexp)
63 | (exchange-point-and-mark))))
64 |
65 | (defun er/mark-inside-python-string ()
66 | "Marks region inside a (possibly multi-line) Python string"
67 | (interactive)
68 | (let ((string-beginning (py-in-string-p)))
69 | (when string-beginning
70 | (goto-char string-beginning)
71 | (forward-sexp)
72 | (skip-chars-backward er--python-string-delimiter)
73 | (set-mark (point))
74 | (goto-char string-beginning)
75 | (skip-chars-forward er--python-string-delimiter))))
76 |
77 | (defun er--move-to-beginning-of-outer-python-block (start-column)
78 | "Assumes that point is in a python block that is surrounded by
79 | another that is not the entire module. Uses `py-indent-offset' to
80 | find the beginning of the surrounding block because
81 | `py-beginning-of-block-position' just looks for the previous
82 | block-starting key word syntactically."
83 | (while (> (current-column) (- start-column py-indent-offset))
84 | (forward-line -1)
85 | (py-beginning-of-block)))
86 |
87 | (defun er/mark-outer-python-block ()
88 | "Attempts to mark a surrounding block by moving to the previous
89 | line and selecting the surrounding block."
90 | (interactive)
91 | (let ((start-column (current-column)))
92 | (when (> start-column 0) ; outer block is the whole buffer
93 | (er--move-to-beginning-of-outer-python-block start-column)
94 | (let ((block-beginning (point)))
95 | (py-end-of-block)
96 | (set-mark (point))
97 | (goto-char block-beginning)))))
98 |
99 | (defun er/mark-x-python-compound-statement ()
100 | "Mark the current compound statement (if, while, for, try) and all clauses."
101 | (interactive)
102 | (let ((secondary-re
103 | (save-excursion
104 | (py-mark-block-or-clause)
105 | (cond ((looking-at "if\\|for\\|while\\|else\\|elif") "else\\|elif")
106 | ((looking-at "try\\|except\\|finally") "except\\|finally"))))
107 | start-col)
108 | (when secondary-re
109 | (py-mark-block-or-clause)
110 | (setq start-col (current-column))
111 | (while (looking-at secondary-re)
112 | (forward-line -1) (back-to-indentation)
113 | (while (> (current-column) start-col)
114 | (forward-line -1) (back-to-indentation)))
115 | (set-mark (point))
116 | (py-end-of-clause-bol) (forward-line) (back-to-indentation)
117 | (while (and (looking-at secondary-re)
118 | (>= (current-column) start-col))
119 | (py-end-of-clause-bol) (forward-line) (back-to-indentation))
120 | (forward-line -1) (end-of-line)
121 | (exchange-point-and-mark))))
122 |
123 | (defun er/add-python-mode-expansions ()
124 | "Adds python-mode-specific expansions for buffers in python-mode"
125 | (let ((try-expand-list-additions '(
126 | er/mark-inside-python-string
127 | er/mark-outside-python-string
128 | py-mark-expression
129 | py-mark-statement
130 | py-mark-block
131 | py-mark-def
132 | py-mark-clause
133 | er/mark-x-python-compound-statement
134 | er/mark-outer-python-block
135 | py-mark-class
136 | )))
137 | (set (make-local-variable 'expand-region-skip-whitespace) nil)
138 | (set (make-local-variable 'er/try-expand-list)
139 | (remove 'er/mark-inside-quotes
140 | (remove 'er/mark-outside-quotes
141 | (append er/try-expand-list try-expand-list-additions))))))
142 |
143 | (er/enable-mode-expansions 'python-mode #'er/add-python-mode-expansions)
144 |
145 | (provide 'python-mode-expansions)
146 |
147 | ;; python-mode-expansions.el ends here
148 |
--------------------------------------------------------------------------------
/features/fgallina-python-el-expansions.feature:
--------------------------------------------------------------------------------
1 | @requires-e24-3
2 | Feature: fgallinas python.el expansions
3 | In order to quickly and precisely mark Python code blocks
4 | As an Emacs user
5 | I want to expand to them
6 |
7 | Scenario: Baseline feature test.
8 | Given I turn on python-mode
9 | And there is no region selected
10 | When I insert "run(23)"
11 | And I place the cursor between "n" and "("
12 | And I press "C-@"
13 | And I press "C-@"
14 | Then the region should be "run(23)"
15 |
16 | Scenario: Mark region inside a string.
17 | Given I turn on python-mode
18 | And there is no region selected
19 | When I insert "'X-Men: Wolverine'"
20 | And I place the cursor between "r" and "i"
21 | And I press "C-@"
22 | And I press "C-@"
23 | Then the region should be "X-Men: Wolverine"
24 |
25 | Scenario: Mark region inside a string with escape delimiter.
26 | Given I turn on python-mode
27 | And there is no region selected
28 | When I insert "'pre' + 'X-Men: Wol\'verine' + 'post'"
29 | And I place the cursor between "r" and "i"
30 | And I press "C-@"
31 | And I press "C-@"
32 | Then the region should be "X-Men: Wol\'verine"
33 |
34 | Scenario: Mark region outside a string.
35 | Given I turn on python-mode
36 | And there is no region selected
37 | When I insert "run('X-Men: ' + 'Wolverine')"
38 | And I place the cursor between "M" and "e"
39 | And I press "C-@"
40 | And I press "C-@"
41 | And I press "C-@"
42 | Then the region should be "'X-Men: '"
43 |
44 | Scenario: Mark region inside a multi-line string.
45 | Given I turn on python-mode
46 | And there is no region selected
47 | When I insert:
48 | """
49 | print('lalelu')
50 |
51 | '''This is a multi-line Python string
52 | with lots of useless content.
53 | '''
54 |
55 | print('lalelu')
56 | """
57 | And I place the cursor between "-" and "l"
58 | And I press "C-@"
59 | And I press "C-@"
60 | Then the region should be:
61 | """
62 | This is a multi-line Python string
63 | with lots of useless content.
64 |
65 | """
66 |
67 | # Scenario: Mark region outside a multi-line string.
68 | # Given I turn on python-mode
69 | # And there is no region selected
70 | # When I insert:
71 | # """
72 | # '''This is a multi-line Python string
73 | # with lots of useless content.
74 | # '''
75 | # """
76 | # And I place the cursor between "-" and "l"
77 | # And I press "C-@"
78 | # And I press "C-@"
79 | # And I press "C-@"
80 | # Then the region should be:
81 | # """
82 | # '''This is a multi-line Python string
83 | # with lots of useless content.
84 | # '''
85 | # """
86 |
87 | Scenario: Mark a basic Python block
88 | Given I turn on python-mode
89 | And there is no region selected
90 | When I insert:
91 | """
92 | if True:
93 | print('To be, or not to be...')
94 | else:
95 | print('Booyah.')
96 | """
97 | And I go to point "1"
98 | And I press "C-@"
99 | And I press "C-@"
100 | And I press "C-@"
101 | Then the region should be:
102 | """
103 | if True:
104 | print('To be, or not to be...')
105 | """
106 |
107 | Scenario: Mark a Python block with a nested block
108 | Given I turn on python-mode
109 | And there is no region selected
110 | When I insert:
111 | """
112 | if True:
113 | if True:
114 | print(23)
115 | print('To be, or not to be...')
116 | else:
117 | print('Booyah.')
118 | """
119 | And I go to point "1"
120 | And I press "C-@"
121 | Then the region should be:
122 | """
123 | if
124 | """
125 | And I press "C-@"
126 | Then the region should be:
127 | """
128 | if True:
129 | """
130 | And I press "C-@"
131 | Then the region should be:
132 | """
133 | if True:
134 | if True:
135 | print(23)
136 | print('To be, or not to be...')
137 | """
138 |
139 | Scenario: Mark another Python block with a nested block
140 | Given I turn on python-mode
141 | And there is no region selected
142 | When I insert:
143 | """
144 | def moo(data):
145 | for foo in data.items():
146 | print(foo)
147 |
148 | """
149 | And I go to point "1"
150 | And I press "C-@"
151 | And I press "C-@"
152 | And I press "C-@"
153 | Then the region should be:
154 | """
155 | def moo(data):
156 | for foo in data.items():
157 | print(foo)
158 | """
159 |
160 | Scenario: Mark an outer Python block
161 | Given I turn on python-mode
162 | And there is no region selected
163 | When I insert:
164 | """
165 | print('More stuff')
166 |
167 | def the_truth():
168 | if True:
169 | print('To be, or not to be...')
170 | else:
171 | print('Booyah.')
172 |
173 | print('Even more stuff.')
174 | """
175 | And I go to the front of the word "if"
176 | And I press "C-@"
177 | Then the region should be:
178 | """
179 | if
180 | """
181 | And I press "C-@"
182 | Then the region should be:
183 | """
184 | if True:
185 | """
186 | And I press "C-@"
187 | Then the region should be:
188 | """
189 | if True:
190 | print('To be, or not to be...')
191 | """
192 | And I press "C-@"
193 | Then the region should be:
194 | """
195 | def the_truth():
196 | if True:
197 | print('To be, or not to be...')
198 | else:
199 | print('Booyah.')
200 | """
201 |
202 | Scenario: Mark nested Python block with subsequent statements in outer block
203 | Given I turn on python-mode
204 | And there is no region selected
205 | When I insert:
206 | """
207 | def outer_foo():
208 |
209 | def inner_foo():
210 | return 23
211 |
212 | return inner_foo()
213 |
214 | """
215 | And I go to point "23"
216 | And I press "C-@"
217 | Then the region should be:
218 | """
219 | def
220 | """
221 | And I press "C-@"
222 | Then the region should be:
223 | """
224 | def inner_foo():
225 | """
226 | And I press "C-@"
227 | Then the region should be:
228 | """
229 | def inner_foo():
230 | return 23
231 | """
232 |
--------------------------------------------------------------------------------
/features/ruby-mode-expansions.feature:
--------------------------------------------------------------------------------
1 | Feature: ruby-mode expansions
2 | In order to quickly and precisely mark ruby code blocks
3 | As an Emacs user
4 | I want to expand to them
5 |
6 | Scenario: Mark instance variable
7 | Given I turn on ruby-mode
8 | When I insert:
9 | """
10 | class Bar
11 | def initialize
12 | @foo = 123
13 | end
14 | end
15 | """
16 | And I place the cursor before "@foo"
17 | And I press "C-@"
18 | Then the region should be "@foo"
19 |
20 | Scenario: Mark ruby block
21 | Given I turn on ruby-mode
22 | And there is no region selected
23 | When I insert:
24 | """
25 | module Bar
26 | something do
27 | foo
28 | end
29 | end
30 | """
31 | And I place the cursor after "something"
32 | And I press "C-@"
33 | And I press "C-@"
34 | Then the region should be:
35 | """
36 | something do
37 | foo
38 | end
39 |
40 | """
41 |
42 | Scenario: Mark ruby block from end
43 | Given I turn on ruby-mode
44 | And there is no region selected
45 | When I insert:
46 | """
47 | module Bar
48 | something do
49 | foo
50 | end
51 | end
52 | """
53 | And I place the cursor after "end"
54 | And I press "C-@"
55 | And I press "C-@"
56 | Then the region should be:
57 | """
58 | something do
59 | foo
60 | end
61 |
62 | """
63 |
64 | Scenario: Mark ruby block from within
65 | Given I turn on ruby-mode
66 | And there is no region selected
67 | When I insert:
68 | """
69 | module Bar
70 | something do
71 | foo
72 | end
73 | end
74 | """
75 | And I go to line "2"
76 | And I press "C-@"
77 | And I press "C-@"
78 | Then the region should be:
79 | """
80 | something do
81 | foo
82 | end
83 |
84 | """
85 |
86 | Scenario: Mark empty ruby block from within
87 | Given I turn on ruby-mode
88 | And there is no region selected
89 | When I insert:
90 | """
91 | module Bar
92 | something do
93 |
94 | end
95 | end
96 | """
97 | And I go to line "3"
98 | And I press "C-@"
99 | And I press "C-@"
100 | Then the region should be:
101 | """
102 | something do
103 |
104 | end
105 |
106 | """
107 |
108 | Scenario: Mark ruby block with using curly brackets
109 | Given I turn on ruby-mode
110 | And there is no region selected
111 | When I insert:
112 | """
113 | module Bar
114 | something {
115 | foo
116 | }
117 | end
118 | """
119 | And I go to line "3"
120 | And I press "C-@"
121 | And I press "C-@"
122 | And I press "C-@"
123 | Then the region should be:
124 | """
125 | something {
126 | foo
127 | }
128 |
129 | """
130 |
131 | Scenario: Mark ruby function at the beginning
132 | Given I turn on ruby-mode
133 | And there is no region selected
134 | When I insert:
135 | """
136 | module Bar
137 | def foo
138 | bar
139 | end
140 | end
141 | """
142 | And I go to word "def"
143 | And I press "C-@"
144 | And I press "C-@"
145 | Then the region should be:
146 | """
147 | def foo
148 | bar
149 | end
150 |
151 | """
152 |
153 | Scenario: Mark ruby function at definition
154 | Given I turn on ruby-mode
155 | And there is no region selected
156 | When I insert:
157 | """
158 | module Bar
159 | def foo
160 | bar
161 | end
162 | end
163 | """
164 | And I go to line "3"
165 | And I press "C-@"
166 | And I press "C-@"
167 | Then the region should be:
168 | """
169 | def foo
170 | bar
171 | end
172 |
173 | """
174 |
175 | Scenario: Mark ruby expand up 1 level
176 | Given I turn on ruby-mode
177 | And there is no region selected
178 | When I insert:
179 | """
180 | #comment foo
181 | module Bar
182 | def foo
183 | bar
184 | end
185 | end
186 |
187 | """
188 | And I go to line "3"
189 | And I press "C-@"
190 | And I press "C-@"
191 | And I press "C-@"
192 | Then the region should be:
193 | """
194 | module Bar
195 | def foo
196 | bar
197 | end
198 | end
199 |
200 | """
201 |
202 | Scenario: Mark ruby expand up 3 levels
203 | Given I turn on ruby-mode
204 | And there is no region selected
205 | When I insert:
206 | """
207 | #comment foo
208 | module Bar
209 |
210 | attr_reader :blah
211 |
212 | foo_arr.each do |element|
213 | blah {
214 | puts something
215 | }
216 | end
217 |
218 | def foo
219 | bar
220 | end
221 | end
222 |
223 | """
224 | And I go to line "8"
225 | And I press "C-@"
226 | And I press "C-@"
227 | And I press "C-@"
228 | And I press "C-@"
229 | And I press "C-@"
230 | And I press "C-@"
231 | Then the region should be:
232 | """
233 | module Bar
234 |
235 | attr_reader :blah
236 |
237 | foo_arr.each do |element|
238 | blah {
239 | puts something
240 | }
241 | end
242 |
243 | def foo
244 | bar
245 | end
246 | end
247 |
248 | """
249 |
250 | Scenario: Mark ruby expand heredoc
251 | Given I turn on ruby-mode
252 | And there is no region selected
253 | When I insert:
254 | """
255 | def foo
256 | blah(<<-end_block)
257 | CONTENT
258 | end_block
259 | end
260 | """
261 | And I place the cursor before "CONTENT"
262 | And I press "C-@"
263 | And I press "C-@"
264 | Then the region should be:
265 | """
266 | CONTENT
267 |
268 | """
269 |
270 | Scenario: Mark ruby expand to whole buffer
271 | Given I turn on ruby-mode
272 | And there is no region selected
273 | When I insert:
274 | """
275 | class Foo
276 | def blah
277 | [1,2,3].each do |num|
278 | puts num
279 | end
280 | end
281 | end
282 |
283 | #comment foo
284 | module Bar
285 | def foo
286 | bar
287 | end
288 | end
289 |
290 | """
291 | And I go to line "12"
292 | And I press "C-@"
293 | And I press "C-@"
294 | And I press "C-@"
295 | And I press "C-@"
296 | Then the region should be:
297 | """
298 | class Foo
299 | def blah
300 | [1,2,3].each do |num|
301 | puts num
302 | end
303 | end
304 | end
305 |
306 | #comment foo
307 | module Bar
308 | def foo
309 | bar
310 | end
311 | end
312 |
313 | """
314 |
--------------------------------------------------------------------------------
/js-mode-expansions.el:
--------------------------------------------------------------------------------
1 | ;;; js-mode-expansions.el --- JS-specific expansions for expand-region -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2011-2023 Free Software Foundation, Inc
4 |
5 | ;; Author: Magnar Sveen
6 | ;; Keywords: marking region
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 |
23 | ;; Extra expansions for JavaScript that I've found useful so far:
24 | ;;
25 | ;; er/mark-js-function
26 | ;; er/mark-js-object-property-value
27 | ;; er/mark-js-object-property
28 | ;; er/mark-js-if
29 | ;; er/mark-js-inner-return
30 | ;; er/mark-js-outer-return
31 | ;;
32 | ;; Feel free to contribute any other expansions for JavaScript at
33 | ;;
34 | ;; https://github.com/magnars/expand-region.el
35 |
36 | ;;; Code:
37 |
38 | (require 'expand-region-core)
39 | (require 'er-basic-expansions)
40 |
41 | (defun er/mark-js-function ()
42 | "Mark the current JavaScript function."
43 | (interactive)
44 | (condition-case nil
45 | (forward-char 8)
46 | (error nil))
47 | (word-search-backward "function")
48 | (while (or (er--point-inside-string-p)
49 | (er--point-is-in-comment-p))
50 | (word-search-backward "function"))
51 | (set-mark (point))
52 | (while (not (looking-at "{"))
53 | (forward-char))
54 | (forward-list)
55 | (exchange-point-and-mark))
56 |
57 | (defun er/mark-js-outer-return ()
58 | "Mark the current return statement, including return and ending semi-colon"
59 | (interactive)
60 | (condition-case nil
61 | (forward-char 6)
62 | (error nil))
63 | (word-search-backward "return")
64 | (while (or (er--point-inside-string-p)
65 | (er--point-is-in-comment-p))
66 | (word-search-backward "return"))
67 | (set-mark (point))
68 | (while (not (looking-at ";"))
69 | (if (looking-at "\\s(")
70 | (forward-list)
71 | (forward-char)))
72 | (forward-char)
73 | (exchange-point-and-mark))
74 |
75 | (defun er/mark-js-inner-return ()
76 | ` "Mark contents of the current return statement.
77 | Does not include return or semi-colon."
78 | (interactive)
79 | (condition-case nil
80 | (forward-char 6)
81 | (error nil))
82 | (word-search-backward "return")
83 | (while (or (er--point-inside-string-p)
84 | (er--point-is-in-comment-p))
85 | (word-search-backward "return"))
86 | (search-forward " ")
87 | (set-mark (point))
88 | (while (not (looking-at ";"))
89 | (if (looking-at "\\s(")
90 | (forward-list)
91 | (forward-char)))
92 | (exchange-point-and-mark))
93 |
94 | (defun er/mark-js-if ()
95 | "Mark the current if-statement."
96 | (interactive)
97 | (condition-case nil
98 | (forward-char 2)
99 | (error nil))
100 | (word-search-backward "if")
101 | (while (or (er--point-inside-string-p)
102 | (er--point-is-in-comment-p))
103 | (word-search-backward "if"))
104 | (set-mark (point))
105 | (while (not (looking-at "("))
106 | (forward-char))
107 | (forward-list)
108 | (while (not (looking-at "{"))
109 | (forward-char))
110 | (forward-list)
111 | (exchange-point-and-mark))
112 |
113 | (defun er/mark-js-object-property-value ()
114 | "Mark the current object property value, ie. from : to , or }"
115 | (interactive)
116 | (unless (er--point-inside-pairs-p)
117 | (error "Point is not inside an object"))
118 | (search-backward ":")
119 | (forward-char)
120 | (search-forward-regexp "[^\s]")
121 | (backward-char)
122 | (set-mark (point))
123 | (while (not (looking-at "[},]"))
124 | (if (looking-at "\\s(")
125 | (forward-list)
126 | (forward-char)))
127 | (when (er/looking-back-max "[\s\n]" 400)
128 | (search-backward-regexp "[^\s\n]")
129 | (forward-char))
130 | (exchange-point-and-mark))
131 |
132 | (defun er/mark-js-object-property ()
133 | "Mark js-object-property.
134 | Presumes that point is at the assignment part of key: value.
135 | If point is inside the value, that will be marked first anyway."
136 | (interactive)
137 | (when (or (looking-at "\"?\\(\\s_\\|\\sw\\| \\)*\":")
138 | (looking-at "\\(\\s_\\|\\sw\\)*:")
139 | (er/looking-back-max ": ?" 2))
140 | (search-backward-regexp "[{,]")
141 | (forward-char)
142 | (search-forward-regexp "[^\s\n]")
143 | (backward-char)
144 | (set-mark (point))
145 | (search-forward ":")
146 | (while (or (not (looking-at "[},]"))
147 | (er--point-inside-string-p))
148 | (if (looking-at "\\s(")
149 | (forward-list)
150 | (forward-char)))
151 | (when (er/looking-back-max "[\s\n]" 400)
152 | (search-backward-regexp "[^\s\n]")
153 | (forward-char))
154 | (exchange-point-and-mark)))
155 |
156 | (defun er/mark-js-call ()
157 | "Mark the current symbol (including dots) and then parens or squares."
158 | (interactive)
159 | (let ((symbol-regexp "\\(\\s_\\|\\sw\\|\\.\\)+"))
160 | (when (or (looking-at symbol-regexp)
161 | (er/looking-back-on-line symbol-regexp))
162 | (skip-syntax-backward "_w.")
163 | (when (looking-at "!")
164 | (forward-char 1))
165 | (set-mark (point))
166 | (when (looking-at symbol-regexp)
167 | (goto-char (match-end 0)))
168 | (if (looking-at "\\[\\|(")
169 | (forward-list))
170 | (exchange-point-and-mark))))
171 |
172 | (defun er/add-js-mode-expansions ()
173 | "Adds JS-specific expansions for buffers in js-mode"
174 | (set (make-local-variable 'er/try-expand-list) (append
175 | er/try-expand-list
176 | '(er/mark-js-function
177 | er/mark-js-object-property-value
178 | er/mark-js-object-property
179 | er/mark-js-if
180 | er/mark-js-inner-return
181 | er/mark-js-outer-return
182 | er/mark-js-call))))
183 |
184 | (er/enable-mode-expansions 'js-mode #'er/add-js-mode-expansions)
185 | (er/enable-mode-expansions 'js2-mode #'er/add-js-mode-expansions)
186 | (er/enable-mode-expansions 'js3-mode #'er/add-js-mode-expansions)
187 |
188 | (provide 'js-mode-expansions)
189 |
190 | ;; js-mode-expansions.el ends here
191 |
--------------------------------------------------------------------------------
/cc-mode-expansions.el:
--------------------------------------------------------------------------------
1 | ;;; cc-mode-expansions.el --- C-specific expansions for expand-region -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc
4 |
5 | ;; Author: François Févotte
6 | ;; Based on js-mode-expansions by: Magnar Sveen
7 | ;; Keywords: marking region
8 |
9 | ;; This program is free software; you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; This program is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with this program. If not, see .
21 |
22 | ;;; Commentary:
23 | ;;
24 | ;; Extra expansions for C-like modes that I've found useful so far:
25 | ;;
26 | ;; er/c-mark-statement
27 | ;; Captures simple and more complex statements.
28 | ;;
29 | ;; er/c-mark-fully-qualified-name
30 | ;; Captures identifiers composed of several '::'-separated parts.
31 | ;;
32 | ;; er/c-mark-function-call[-1|-2]
33 | ;; Captures an identifier followed by a '()'-enclosed block.
34 | ;;
35 | ;; er/c-mark-statement-block[-1|-2]
36 | ;; Captures a statement followed by a '{}'-enclosed block.
37 | ;; This matches function definitions and if/for/... constructs.
38 | ;;
39 | ;; er/c-mark-vector-access[-1|-2]
40 | ;; Captures an identifier followed by a '[]'-enclosed block.
41 | ;;
42 | ;; Feel free to contribute any other expansions for C at
43 | ;;
44 | ;; https://github.com/magnars/expand-region.el
45 |
46 | ;;; Code:
47 |
48 | (require 'expand-region-core)
49 | (require 'er-basic-expansions)
50 | (require 'cc-cmds)
51 |
52 | (defun er/c-mark-statement ()
53 | "Mark the current C statement.
54 |
55 | This function tries to ensure that pair-delimited substring are
56 | either fully inside or fully outside the statement."
57 | (interactive)
58 | (unless (use-region-p)
59 | (set-mark (point)))
60 |
61 | (if (< (point) (mark))
62 | (exchange-point-and-mark))
63 |
64 | ;; Contract the region a bit to make the
65 | ;; er/c-mark-statement function idempotent
66 | (when (>= (- (point) (mark)) 2)
67 | (exchange-point-and-mark)
68 | (forward-char)
69 | (exchange-point-and-mark)
70 | (backward-char))
71 |
72 | (let (beg end)
73 | ;; Determine boundaries of the outside-pairs region
74 | (save-mark-and-excursion
75 | (c-end-of-statement)
76 | (er/mark-outside-pairs)
77 | (setq beg (point)
78 | end (mark)))
79 |
80 | ;; Determine boundaries of the statement as given
81 | ;; by c-beginning-of-statement/c-end-of-statement
82 | (c-end-of-statement)
83 | (exchange-point-and-mark)
84 | (c-end-of-statement)(c-beginning-of-statement 1)
85 |
86 | ;; If the two regions overlap, expand the region
87 | (cond ((and (<= (point) beg)
88 | (< (mark) end))
89 | (set-mark end))
90 | ((and (> (point) beg)
91 | (>= (mark) end))
92 | (goto-char beg)
93 | (c-end-of-statement)
94 | (c-beginning-of-statement 1)))))
95 |
96 | (defun er/c-mark-fully-qualified-name ()
97 | "Mark the current C++ fully qualified identifier.
98 |
99 | This function captures identifiers composed of multiple
100 | '::'-separated parts."
101 | (interactive)
102 | (er/mark-symbol)
103 | (when (use-region-p)
104 | (when (> (point) (mark))
105 | (exchange-point-and-mark))
106 | (while (er/looking-back-exact "::")
107 | (backward-char 2)
108 | (skip-syntax-backward "_w"))
109 | (exchange-point-and-mark)
110 | (while (looking-at "::")
111 | (forward-char 2)
112 | (skip-syntax-forward "_w"))
113 | (exchange-point-and-mark)))
114 |
115 | (defmacro er/c-define-construct (name mark-first-part open-brace doc)
116 | (let ((docstring (make-symbol "docstring-tmp")))
117 | (setq docstring
118 | (concat
119 | doc "\n\n"
120 | "This function tries to mark a region consisting of two parts:\n"
121 | (format " - the first part is marked using `%s'\n" (symbol-name mark-first-part))
122 | (format " - the second part is a block beginning with %S\n\n" open-brace)))
123 | `(progn
124 | (defun ,(intern (concat (symbol-name name) "-1")) ()
125 | ,(concat docstring
126 | "This function assumes that point is in the first part and the\n"
127 | "region is active.\n\n"
128 | (format "See also `%s'." (concat (symbol-name name) "-2")))
129 | (interactive)
130 | (when (use-region-p)
131 | (,mark-first-part)
132 | (exchange-point-and-mark)
133 | (let ((oldpos (point)))
134 | (skip-syntax-forward " ")
135 | (if (looking-at ,open-brace)
136 | (progn (forward-sexp)
137 | (exchange-point-and-mark))
138 | (goto-char oldpos)))))
139 | (defun ,(intern (concat (symbol-name name) "-2")) ()
140 | ,(concat docstring
141 | "This function assumes that the block constituting the second part\n"
142 | "is already marked and active.\n\n"
143 | (format "See also `%s'." (concat (symbol-name name) "-1")))
144 | (interactive)
145 | (when (use-region-p)
146 | (when (> (point) (mark))
147 | (exchange-point-and-mark))
148 | (when (looking-at ,open-brace)
149 | (let ((beg (point))
150 | (end (progn (forward-sexp 1)
151 | (point))))
152 | (goto-char beg)
153 | (skip-syntax-backward " ")
154 | (backward-char)
155 | (deactivate-mark)
156 | (,mark-first-part)
157 | (set-mark end))))))))
158 |
159 | (er/c-define-construct er/c-mark-function-call er/c-mark-fully-qualified-name "("
160 | "Mark the current function call.")
161 | (er/c-define-construct er/c-mark-statement-block er/c-mark-statement "{"
162 | "Mark the current block construct (like if, for, etc.)")
163 | (er/c-define-construct er/c-mark-vector-access er/c-mark-fully-qualified-name "\\["
164 | "Mark the current vector access.")
165 |
166 | (defun er/add-cc-mode-expansions ()
167 | "Adds expansions for buffers in c-mode."
168 | (set (make-local-variable 'er/try-expand-list)
169 | (append er/try-expand-list
170 | '(er/c-mark-statement
171 | er/c-mark-fully-qualified-name
172 | er/c-mark-function-call-1 er/c-mark-function-call-2
173 | er/c-mark-statement-block-1 er/c-mark-statement-block-2
174 | er/c-mark-vector-access-1 er/c-mark-vector-access-2))))
175 |
176 | (er/enable-mode-expansions 'c-mode #'er/add-cc-mode-expansions)
177 | (er/enable-mode-expansions 'c++-mode #'er/add-cc-mode-expansions)
178 | (er/enable-mode-expansions 'objc-mode #'er/add-cc-mode-expansions)
179 | (er/enable-mode-expansions 'java-mode #'er/add-cc-mode-expansions)
180 | (er/enable-mode-expansions 'idl-mode #'er/add-cc-mode-expansions)
181 | (er/enable-mode-expansions 'pike-mode #'er/add-cc-mode-expansions)
182 | (er/enable-mode-expansions 'awk-mode #'er/add-cc-mode-expansions)
183 |
184 | (provide 'cc-mode-expansions)
185 |
186 | ;; cc-mode-expansions.el ends here
187 |
--------------------------------------------------------------------------------
/ruby-mode-expansions.el:
--------------------------------------------------------------------------------
1 | ;;; ruby-mode-expansions.el --- ruby-specific expansions for expand-region -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2011-2023 Free Software Foundation, Inc
4 |
5 | ;; Author: Matt Briggs
6 | ;; Based on js-mode-expansions by: Magnar Sveen
7 | ;; Keywords: marking region
8 |
9 | ;; This program is free software; you can redistribute it and/or modify
10 | ;; it under the terms of the GNU General Public License as published by
11 | ;; the Free Software Foundation, either version 3 of the License, or
12 | ;; (at your option) any later version.
13 |
14 | ;; This program is distributed in the hope that it will be useful,
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | ;; GNU General Public License for more details.
18 |
19 | ;; You should have received a copy of the GNU General Public License
20 | ;; along with this program. If not, see .
21 |
22 | ;;; Commentary:
23 |
24 |
25 | ;; LeWang:
26 | ;;
27 | ;; I think `er/ruby-backward-up' and `er/ruby-forward-up' are nifty
28 | ;; functions in their own right.
29 | ;;
30 | ;; I would bind them to C-M-u and C-M-d respectively.
31 |
32 | ;; Expansions:
33 | ;;
34 | ;;
35 | ;; er/mark-ruby-block-up
36 | ;;
37 |
38 | ;;; Code:
39 | (eval-when-compile (require 'cl-lib))
40 | (require 'expand-region-core)
41 | (require 'er-basic-expansions)
42 | (require 'ruby-mode)
43 |
44 | (defvar er/ruby-block-end-re
45 | (concat ruby-block-end-re "\\|}")
46 | "like ruby-mode's but also for '}'")
47 |
48 | (defun er/ruby-skip-past-block-end ()
49 | "If line is blockend, move point to next line."
50 | (when (looking-at er/ruby-block-end-re)
51 | (forward-line 1)))
52 |
53 | (defun er/ruby-end-of-block (&optional arg)
54 | "By default `ruby-end-of-block' goes to BOL of line containing end-re.
55 |
56 | This moves point to the next line to include the end of the block"
57 | (interactive "p")
58 | ;; Workaround for `ruby-end-of-block' in Emacs 23.
59 | (when (re-search-forward (concat "\\<\\(" ruby-block-beg-re "\\)\\>")
60 | (line-end-position) t)
61 | (goto-char (match-beginning 0)))
62 | (ruby-end-of-block (or arg 1))
63 | (er/ruby-skip-past-block-end))
64 |
65 | (defun er/point-at-indentation ()
66 | "Return the point where current line's indentation ends."
67 | (save-excursion
68 | (back-to-indentation)
69 | (point)))
70 |
71 | (defun er/ruby-backward-up ()
72 | "a la `paredit-backward-up'"
73 | (interactive)
74 | ;; if our current line ends a block, we back a line, otherwise we
75 | (when (save-excursion
76 | (back-to-indentation)
77 | (looking-at-p ruby-block-end-re))
78 | (forward-line -1))
79 | (let ((orig-point (point))
80 | progress-beg
81 | progress-end)
82 |
83 | ;; cover the case when point is in the line of beginning of block
84 | (unless (progn (ruby-end-of-block)
85 | (ruby-beginning-of-block)
86 | ;; "Block beginning" is often not at indentation in Emacs 24.
87 | (< (er/point-at-indentation) orig-point))
88 | (cl-loop
89 | (ruby-beginning-of-block)
90 | (setq progress-beg (point))
91 | (when (= (point) (point-min))
92 | (cl-return))
93 | (ruby-end-of-block)
94 | (setq progress-end (line-beginning-position
95 | (if (looking-at-p er/ruby-block-end-re) 0 1)))
96 | (goto-char progress-beg)
97 | (when (> progress-end orig-point)
98 | (cl-return))))))
99 |
100 | ;; This command isn't used here explicitly, but it's symmetrical with
101 | ;; `er/ruby-backward-up', and nifty for interactive use.
102 | (defun er/ruby-forward-up ()
103 | "a la `paredit-forward-up'"
104 | (interactive)
105 | (er/ruby-backward-up)
106 | (er/ruby-end-of-block))
107 |
108 | (defun er/get-ruby-block (&optional pos)
109 | "return (beg . end) of current block"
110 | (setq pos (or pos (point)))
111 | (save-excursion
112 | (goto-char pos)
113 | (cons (progn
114 | (er/ruby-backward-up)
115 | (er/point-at-indentation))
116 | (progn
117 | (er/ruby-end-of-block)
118 | (point)))))
119 |
120 | (defun er/mark-ruby-block-up-1 ()
121 | (er/ruby-backward-up)
122 | (set-mark (er/point-at-indentation))
123 | (er/ruby-end-of-block)
124 | (exchange-point-and-mark))
125 |
126 | (defun er/mark-ruby-block-up (&optional no-recurse)
127 | "mark the next level up."
128 | (interactive)
129 | (if (use-region-p)
130 | (let* ((orig-end (region-end))
131 | (orig-beg (region-beginning))
132 | (orig-len (- orig-end orig-beg))
133 | (prev-block-point
134 | (or (save-excursion
135 | (goto-char orig-end)
136 | (forward-line 0)
137 | (back-to-indentation)
138 | (cond ((looking-at-p er/ruby-block-end-re)
139 | (line-beginning-position 0))
140 | ((re-search-forward
141 | (concat "\\<\\(" ruby-block-beg-re "\\)\\>")
142 | (line-end-position)
143 | t)
144 | (line-beginning-position 2))) )
145 | (point)))
146 | (prev-block-info (er/get-ruby-block prev-block-point))
147 | (prev-block-beg (car prev-block-info))
148 | (prev-block-end (cdr prev-block-info))
149 | (prev-block-len (- prev-block-end prev-block-beg)))
150 | (if (and (>= orig-beg prev-block-beg)
151 | (<= orig-end prev-block-end)
152 | (< orig-len prev-block-len))
153 | ;; expand to previous block if it contains and grows current
154 | ;; region
155 | (progn
156 | (deactivate-mark)
157 | (goto-char prev-block-point)
158 | (or no-recurse
159 | (er/mark-ruby-block-up 'no-recurse)))
160 | (er/mark-ruby-block-up-1)))
161 | (er/mark-ruby-block-up-1)))
162 |
163 | (defun er/mark-ruby-instance-variable ()
164 | "Marks instance variables in ruby.
165 | Assumes that point is at the @ - if it is inside the word, that will
166 | be marked first anyway."
167 | (when (looking-at "@")
168 | (forward-char 1))
169 | (when (er/looking-back-exact "@")
170 | (er/mark-symbol)
171 | (forward-char -1)))
172 |
173 | (defun er/mark-ruby-heredoc ()
174 | "Marks a heredoc, since `er/mark-inside-quotes' assumes single quote chars."
175 | (let ((ppss (syntax-ppss)))
176 | (when (elt ppss 3)
177 | (let ((s-start (elt ppss 8)))
178 | (goto-char s-start)
179 | (when (save-excursion
180 | (beginning-of-line)
181 | (re-search-forward "<<\\(-?\\)['\"]?\\([a-zA-Z0-9_]+\\)" s-start nil))
182 | (let ((allow-indent (string= "-" (match-string 1)))
183 | (terminator (match-string 2))
184 | (heredoc-start (save-excursion
185 | (forward-line)
186 | (point))))
187 | (forward-sexp 1)
188 | (forward-line -1)
189 | (when (looking-at (concat "^" (if allow-indent "[ \t]*" "") terminator "$"))
190 | (set-mark heredoc-start)
191 | (exchange-point-and-mark))))))))
192 |
193 | (defun er/add-ruby-mode-expansions ()
194 | "Adds Ruby-specific expansions for buffers in ruby-mode"
195 | (set (make-local-variable 'er/try-expand-list)
196 | (remove 'er/mark-defun
197 | (append
198 | (default-value 'er/try-expand-list)
199 | '(er/mark-ruby-instance-variable
200 | er/mark-ruby-block-up
201 | er/mark-ruby-heredoc)))))
202 |
203 | (er/enable-mode-expansions 'ruby-mode #'er/add-ruby-mode-expansions)
204 | (provide 'ruby-mode-expansions)
205 |
--------------------------------------------------------------------------------
/python-el-fgallina-expansions.el:
--------------------------------------------------------------------------------
1 | ;;; python-el-fgallina-expansions.el --- fgallina/python.el-specific expansions for expand-region -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc
4 |
5 | ;; Author: Felix Geller
6 | ;; Keywords: marking region python
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 | ;;
23 | ;; - Additions implemented here:
24 | ;; - `er/mark-inside-python-string'
25 | ;; - `er/mark-outside-python-string'
26 | ;; - `er/mark-python-statement'
27 | ;; - `er/mark-python-block'
28 | ;; - `er/mark-outer-python-block'
29 | ;; - `er/mark-python-block-and-decorator'
30 | ;; - Supports multi-line strings
31 |
32 | ;;; Code:
33 |
34 | (require 'expand-region-core)
35 |
36 | (if (not (fboundp 'python-syntax-context))
37 | (defalias 'python-syntax-context #'python-info-ppss-context))
38 | (if (not (fboundp 'python-indent-offset))
39 | (defalias 'python-indent-offset #'python-indent))
40 |
41 | (defvar er--python-string-delimiter
42 | "'\""
43 | "Characters that delimit a Python string.")
44 |
45 | ;; copied from @fgallina's python.el as a quick fix. The variable
46 | ;; `python-rx-constituents' is not bound when we use the python-rx
47 | ;; macro from here, so we have to construct the regular expression
48 | ;; manually.
49 | (defvar er--python-block-start-regex
50 | (rx symbol-start
51 | (or "def" "class" "if" "elif" "else" "try"
52 | "except" "finally" "for" "while" "with")
53 | symbol-end)
54 | "Regular expression string to match the beginning of a Python block.")
55 |
56 | (defun er/mark-python-string (mark-inside)
57 | "Mark the Python string that surrounds point.
58 |
59 | If the optional MARK-INSIDE is not nil, only mark the region
60 | between the string delimiters, otherwise the region includes the
61 | delimiters as well."
62 | (let ((beginning-of-string (python-syntax-context 'string (syntax-ppss))))
63 | (when beginning-of-string
64 | (goto-char beginning-of-string)
65 | ;; Move inside the string, so we can use ppss to find the end of
66 | ;; the string.
67 | (skip-chars-forward er--python-string-delimiter)
68 | (while (python-syntax-context 'string (syntax-ppss))
69 | (forward-char 1))
70 | (when mark-inside (skip-chars-backward er--python-string-delimiter))
71 | (set-mark (point))
72 | (goto-char beginning-of-string)
73 | (when mark-inside (skip-chars-forward er--python-string-delimiter)))))
74 |
75 | (defun er/mark-inside-python-string ()
76 | "Mark the inside of the Python string that surrounds point.
77 |
78 | Command that wraps `er/mark-python-string'."
79 | (interactive)
80 | (er/mark-python-string t))
81 |
82 | (defun er/mark-outside-python-string ()
83 | "Mark the outside of the Python string that surrounds point.
84 |
85 | Command that wraps `er/mark-python-string'."
86 | (interactive)
87 | (er/mark-python-string nil))
88 |
89 | (defun er/mark-python-statement ()
90 | "Mark the Python statement that surrounds point."
91 | (interactive)
92 | (python-nav-end-of-statement)
93 | (set-mark (point))
94 | (python-nav-beginning-of-statement))
95 |
96 | (defun er/mark-python-block (&optional next-indent-level)
97 | "Mark the Python block that surrounds point.
98 |
99 | If the optional NEXT-INDENT-LEVEL is given, select the
100 | surrounding block that is defined at an indentation that is less
101 | than NEXT-INDENT-LEVEL."
102 | (interactive)
103 | (back-to-indentation)
104 | (let ((next-indent-level
105 | (or
106 | ;; Use the given level
107 | next-indent-level
108 | ;; Check whether point is at the start of a Python block.
109 | (if (looking-at er--python-block-start-regex)
110 | ;; Block start means that the next level is deeper.
111 | (+ (current-indentation) python-indent-offset)
112 | ;; Assuming we're inside the block that we want to mark
113 | (current-indentation)))))
114 | ;; Move point to next Python block start at the correct indent-level
115 | (while (>= (current-indentation) next-indent-level)
116 | (re-search-backward er--python-block-start-regex))
117 | ;; Mark the beginning of the block
118 | (set-mark (point))
119 | ;; Save indentation and look for the end of this block
120 | (let ((block-indentation (current-indentation)))
121 | (forward-line 1)
122 | (while (and
123 | ;; No need to go beyond the end of the buffer. Can't use
124 | ;; eobp as the loop places the point at the beginning of
125 | ;; line, but eob might be at the end of the line.
126 | (not (= (point-max) (line-end-position)))
127 | ;; Proceed if: indentation is too deep
128 | (or (> (current-indentation) block-indentation)
129 | ;; Looking at an empty line
130 | (looking-at (rx line-start (* whitespace) line-end))
131 | ;; We're not looking at the start of a Python block
132 | ;; and the indent is deeper than the block's indent
133 | (and (not (looking-at er--python-block-start-regex))
134 | (> (current-indentation) block-indentation))))
135 | (forward-line 1)
136 | (back-to-indentation))
137 | ;; Find the end of the block by skipping comments backwards
138 | (python-util-forward-comment -1)
139 | (exchange-point-and-mark))))
140 |
141 | (defun er/mark-outer-python-block ()
142 | "Mark the Python block that surrounds the Python block around point.
143 |
144 | Command that wraps `er/mark-python-block'."
145 | (interactive)
146 | (er/mark-python-block (current-indentation)))
147 |
148 | (defun er/mark-python-block-and-decorator ()
149 | (interactive)
150 | (back-to-indentation)
151 | (if (or (er--python-looking-at-decorator) (er--python-looking-at-decorator -1))
152 | (progn
153 | (while (er--python-looking-at-decorator -1)
154 | (forward-line -1)
155 | (back-to-indentation)
156 | )
157 | (set-mark (point))
158 | (while (er--python-looking-at-decorator)
159 | (forward-line)
160 | )
161 | (python-nav-end-of-block)
162 | (exchange-point-and-mark))))
163 |
164 | (defun er--python-looking-at-decorator (&optional line-offset)
165 | (save-excursion
166 | (if line-offset
167 | (forward-line line-offset)
168 | )
169 | (back-to-indentation)
170 | (looking-at "@")
171 | ))
172 |
173 | (defun er/add-python-mode-expansions ()
174 | "Adds python-mode-specific expansions for buffers in python-mode"
175 | (let ((try-expand-list-additions '(
176 | er/mark-inside-python-string
177 | er/mark-outside-python-string
178 | er/mark-python-statement
179 | er/mark-python-block
180 | er/mark-python-block-and-decorator
181 | er/mark-outer-python-block
182 | )))
183 | (set (make-local-variable 'expand-region-skip-whitespace) nil)
184 | (set (make-local-variable 'er/try-expand-list)
185 | (remove 'er/mark-inside-quotes
186 | (remove 'er/mark-outside-quotes
187 | (append er/try-expand-list try-expand-list-additions))))))
188 |
189 | (er/enable-mode-expansions 'python-mode #'er/add-python-mode-expansions)
190 |
191 | (provide 'python-el-fgallina-expansions)
192 |
193 | ;; python-el-fgallina-expansions.el ends here
194 |
--------------------------------------------------------------------------------
/yaml-mode-expansions.el:
--------------------------------------------------------------------------------
1 | ;;; yaml-mode-expansions.el --- expansions for yaml mode -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2021-2023 Free Software Foundation, Inc.
4 |
5 | ;; Author: Aaron Gonzales
6 | ;; Keywords: marking region yaml YAML expand
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 | ;;
23 | ;; - Additions implemented here:
24 | ;; - er/mark-yaml-key-value
25 | ;; - er/mark-yaml-list-item
26 | ;; - er/mark-yaml-block
27 | ;; - er/mark-yaml-outer-block
28 | ;; - er/mark-yaml-inner-block
29 |
30 |
31 | ;;; Code:
32 |
33 | (require 'expand-region-core)
34 |
35 | (defconst yaml-indent 2)
36 |
37 | (unless (fboundp 'yaml-indent-offset)
38 | (defalias 'yaml-indent-offset #'yaml-indent))
39 |
40 | (defvar er--yaml-key-value-regex
41 | (rx (one-or-more
42 | (any "0-9A-Za-z"))
43 | ":"
44 | (zero-or-more " ")
45 | (one-or-more
46 | (any "0-9A-Za-z" " '_-"))))
47 |
48 | (defvar er--yaml-list-item-regex
49 | (rx (seq "- "
50 | (one-or-more
51 | (any "0-9A-Za-z" "\"':=_-")))))
52 |
53 | (defvar er--yaml-block-regex
54 | (rx (seq (zero-or-more
55 | (any " -"))
56 | (one-or-more
57 | (any "0-9A-Za-z" " '_-"))
58 | ":\n")))
59 |
60 | (defun er--get-regex-indentation-level (regex)
61 | "Return the indentation level of the code with respect to the REGEX passed."
62 | (when (looking-at regex)
63 | ;; Block start means that the next level is deeper.
64 | (+ (current-indentation) yaml-indent-offset) ;FIXME: Unused?
65 | ;; Assuming we're inside the block that we want to mark
66 | (current-indentation)))
67 |
68 | (defun er/mark-yaml-line-base (regex)
69 | "Mark line of yaml file based on simple REGEX."
70 | (back-to-indentation)
71 | (when (looking-at regex)
72 | (set-mark (line-end-position))))
73 |
74 | (defun er/mark-yaml-block-static-base (regex)
75 | "Mark yaml block based on REGEX passed."
76 | ;; go bac to indentation so always can get regexp
77 | (back-to-indentation)
78 | ;; make sure the cursor is set inside the block
79 | ;; mark point at this higher code block
80 | (set-mark (point))
81 | ;; save level of this blocks indentation
82 | (let ((block-indentation (current-indentation)))
83 | (forward-line 1)
84 | (while (and
85 | ;; No need to go beyond the end of the buffer. Can't use
86 | ;; eobp as the loop places the point at the beginning of
87 | ;; line, but eob might be at the end of the line.
88 | (not (= (point-max) (line-end-position)))
89 | ;; Proceed if: indentation is too deep
90 | (or (> (current-indentation) block-indentation)
91 | ;; Looking at an empty line
92 | (looking-at (rx line-start (* whitespace) line-end))
93 | ;; We're not looking at the start of a YAML block
94 | ;; and the indent is deeper than the block's indent
95 | (and (not (looking-at regex))
96 | (> (current-indentation) block-indentation))))
97 | (forward-line 1)
98 | (back-to-indentation))
99 | ;; Find the end of the block by skipping comments backwards
100 | (python-util-forward-comment -1)
101 | (exchange-point-and-mark))
102 | (back-to-indentation))
103 |
104 | (defun er/mark-yaml-block-base (regex &optional next-indent-level)
105 | "Mark yaml block based on REGEX passed.
106 | NEXT-INDENT-LEVEL can be used to search outer blocks when necessary."
107 | ;; go bac to indentation so always can get regexp
108 | (back-to-indentation)
109 | ;; make sure the cursor is set inside the block
110 | (let ((next-indent-level
111 | (or
112 | ;; Use the given level
113 | next-indent-level
114 | ;; used to mark current block
115 | (er--get-regex-indentation-level regex))))
116 | ;; if true then at start of block and wanna mark itself
117 | ;; else were are inside the block already and will mark it)))
118 | ;; move up the code unti a parent code block is reached
119 | (while (and (>= (current-indentation) next-indent-level)
120 | (not (eq (current-indentation) 0)))
121 | (re-search-backward regex (point-min) t)
122 | (back-to-indentation))
123 | ;; mark point at this higher code block
124 | (set-mark (point))
125 | ;; save level of this blocks indentation
126 | (let ((block-indentation (current-indentation)))
127 | (forward-line 1)
128 | (while (and
129 | ;; No need to go beyond the end of the buffer. Can't use
130 | ;; eobp as the loop places the point at the beginning of
131 | ;; line, but eob might be at the end of the line.
132 | (not (= (point-max) (line-end-position)))
133 | ;; Proceed if: indentation is too deep
134 | (or (> (current-indentation) block-indentation)
135 | ;; Looking at an empty line
136 | (looking-at (rx line-start (* whitespace) line-end))
137 | ;; We're not looking at the start of a YAML block
138 | ;; and the indent is deeper than the block's indent
139 | (and (not (looking-at regex))
140 | (> (current-indentation) block-indentation))))
141 | (forward-line 1)
142 | (back-to-indentation))
143 | ;; Find the end of the block by skipping comments backwards
144 | (python-util-forward-comment -1)
145 | (exchange-point-and-mark)))
146 | (back-to-indentation))
147 |
148 | (defun er/mark-yaml-key-value ()
149 | "Mark a yaml key-value pair."
150 | (interactive)
151 | (er/mark-yaml-line-base er--yaml-key-value-regex))
152 |
153 | (defun er/mark-yaml-list-item ()
154 | "Mark a yaml list item."
155 | (interactive)
156 | (er/mark-yaml-line-base er--yaml-list-item-regex))
157 |
158 | (defun er/mark-yaml-inner-block ()
159 | "Mark the yaml contents of the block at point.
160 | Command that wraps `er/mark-yaml-block-base'."
161 | (interactive)
162 | (er/mark-yaml-block-base er--yaml-block-regex (current-indentation))
163 | (forward-line)
164 | (back-to-indentation))
165 |
166 | (defun er/mark-yaml-block ()
167 | "Mark the yaml block that point is currently at the top of.
168 | Command that wraps `er/mark-yaml-block-base'."
169 | (interactive)
170 | (er/mark-yaml-block-static-base er--yaml-block-regex))
171 |
172 | (defun er/mark-yaml-outer-block ()
173 | "Mark the outer yaml block that surrounds the block around point.
174 | Command that wraps `er/mark-yaml-block-base'."
175 | (interactive)
176 | (er/mark-yaml-block-base er--yaml-block-regex (current-indentation)))
177 |
178 | (defun er/add-yaml-mode-expansions ()
179 | "Add yaml-mode-specific expansions for buffers in yaml-mode."
180 | (let ((try-expand-list-additions '(er/mark-symbol
181 | er/mark-outside-quotes
182 | er/mark-yaml-list-item
183 | er/mark-yaml-key-value
184 | er/mark-yaml-block
185 | er/mark-yaml-outer-block
186 | er/mark-yaml-inner-block)))
187 | (set (make-local-variable 'expand-region-skip-whitespace) nil)
188 | (set (make-local-variable 'er/try-expand-list) try-expand-list-additions)))
189 |
190 | (er/enable-mode-expansions 'yaml-mode #'er/add-yaml-mode-expansions)
191 |
192 | (provide 'yaml-mode-expansions)
193 |
194 | ;;; yaml-mode-expansions.el ends here
195 |
--------------------------------------------------------------------------------
/expand-region.el:
--------------------------------------------------------------------------------
1 | ;;; expand-region.el --- Increase selected region by semantic units. -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2011-2023 Free Software Foundation, Inc
4 |
5 | ;; Author: Magnar Sveen
6 | ;; Keywords: marking region
7 | ;; URL: https://github.com/magnars/expand-region.el
8 | ;; Version: 1.0.0
9 | ;; Package-Requires: ((emacs "24.4"))
10 |
11 | ;; This program is free software; you can redistribute it and/or modify
12 | ;; it under the terms of the GNU General Public License as published by
13 | ;; the Free Software Foundation, either version 3 of the License, or
14 | ;; (at your option) any later version.
15 |
16 | ;; This program is distributed in the hope that it will be useful,
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | ;; GNU General Public License for more details.
20 |
21 | ;; You should have received a copy of the GNU General Public License
22 | ;; along with this program. If not, see .
23 |
24 | ;;; Commentary:
25 |
26 | ;; Expand region increases the selected region by semantic units. Just keep
27 | ;; pressing the key until it selects what you want.
28 |
29 | ;; An example:
30 |
31 | ;; (setq alphabet-start "abc def")
32 |
33 | ;; With the cursor at the `c`, it starts by marking the entire word `abc`, then
34 | ;; expand to the contents of the quotes `abc def`, then to the entire quote
35 | ;; `"abc def"`, then to the contents of the sexp `setq alphabet-start "abc def"`
36 | ;; and finally to the entire sexp.
37 |
38 | ;; You can set it up like this:
39 |
40 | ;; (require 'expand-region)
41 | ;; (global-set-key (kbd "C-=") 'er/expand-region)
42 |
43 | ;; There's also `er/contract-region` if you expand too far.
44 |
45 | ;; ## Video
46 |
47 | ;; You can [watch an intro to expand-region at Emacs Rocks](http://emacsrocks.com/e09.html).
48 |
49 | ;; ## Language support
50 |
51 | ;; Expand region works fairly well with most languages, due to the general
52 | ;; nature of the basic expansions:
53 |
54 | ;; er/mark-word
55 | ;; er/mark-symbol
56 | ;; er/mark-method-call
57 | ;; er/mark-inside-quotes
58 | ;; er/mark-outside-quotes
59 | ;; er/mark-inside-pairs
60 | ;; er/mark-outside-pairs
61 |
62 | ;; However, most languages also will benefit from some specially crafted
63 | ;; expansions. For instance, expand-region comes with these extra expansions for
64 | ;; html-mode:
65 |
66 | ;; er/mark-html-attribute
67 | ;; er/mark-inner-tag
68 | ;; er/mark-outer-tag
69 |
70 | ;; You can add your own expansions to the languages of your choice simply by
71 | ;; creating a function that looks around point to see if it's inside or looking
72 | ;; at the construct you want to mark, and if so - mark it.
73 |
74 | ;; There's plenty of examples to look at in these files.
75 |
76 | ;; After you make your function, add it to a buffer-local version of
77 | ;; the `er/try-expand-list`.
78 |
79 | ;; **Example:**
80 |
81 | ;; Let's say you want expand-region to also mark paragraphs and pages in
82 | ;; text-mode. Incidentally Emacs already comes with `mark-paragraph` and
83 | ;; `mark-page`. To add it to the try-list, do this:
84 |
85 | ;; (defun er/add-text-mode-expansions ()
86 | ;; (setq-local er/try-expand-list (append
87 | ;; er/try-expand-list
88 | ;; '(mark-paragraph
89 | ;; mark-page))))
90 |
91 | ;; (er/enable-mode-expansions 'text-mode #'er/add-text-mode-expansions)
92 |
93 | ;; Add that to its own file, and require it at the bottom of this one,
94 | ;; where it says "Mode-specific expansions"
95 |
96 | ;; **Warning:** Badly written expansions might slow down expand-region
97 | ;; dramatically. Remember to exit quickly before you start traversing
98 | ;; the entire document looking for constructs to mark.
99 |
100 | ;; ## Contribute
101 |
102 | ;; If you make some nice expansions for your favorite mode, it would be
103 | ;; great if you opened a pull-request. The repo is at:
104 |
105 | ;; https://github.com/magnars/expand-region.el
106 |
107 | ;; Changes to `expand-region-core` itself must be accompanied by feature tests.
108 | ;; They are written in [Ecukes](http://ecukes.info), a Cucumber for Emacs.
109 |
110 | ;; To fetch the test dependencies:
111 |
112 | ;; $ cd /path/to/expand-region
113 | ;; $ git submodule init
114 | ;; $ git submodule update
115 |
116 | ;; Run the tests with:
117 |
118 | ;; $ ./util/ecukes/ecukes features
119 |
120 | ;; If you want to add feature-tests for your mode-specific expansions as well,
121 | ;; that is utterly excellent.
122 |
123 | ;; ## Contributors
124 |
125 | ;; * [Josh Johnston](https://github.com/joshwnj) contributed `er/contract-region`
126 | ;; * [Le Wang](https://github.com/lewang) contributed consistent handling of the mark ring, expanding into pairs/quotes just left of the cursor, and general code clean-up.
127 | ;; * [Matt Briggs](https://github.com/mbriggs) contributed expansions for ruby-mode.
128 | ;; * [Ivan Andrus](https://github.com/gvol) contributed expansions for python-mode, text-mode, LaTeX-mode and nxml-mode.
129 | ;; * [Raimon Grau](https://github.com/kidd) added support for when transient-mark-mode is off.
130 | ;; * [Gleb Peregud](https://github.com/gleber) contributed expansions for erlang-mode.
131 | ;; * [fgeller](https://github.com/fgeller) and [edmccard](https://github.com/edmccard) contributed better support for python and its multiple modes.
132 | ;; * [François Févotte](https://github.com/ffevotte) contributed expansions for C and C++.
133 | ;; * [Roland Walker](https://github.com/rolandwalker) added option to copy the contents of the most recent action to a register, and some fixes.
134 | ;; * [Damien Cassou](https://github.com/DamienCassou) added option to continue expanding/contracting with fast keys after initial expand.
135 |
136 | ;; Thanks!
137 |
138 | ;;; Code:
139 |
140 | (require 'expand-region-core)
141 | (require 'expand-region-custom)
142 | (require 'er-basic-expansions)
143 |
144 | ;;;###autoload
145 | (defun er/expand-region (arg)
146 | "Increase selected region by semantic units.
147 |
148 | With prefix argument expands the region that many times.
149 | If prefix argument is negative calls `er/contract-region'.
150 | If prefix argument is 0 it resets point and mark to their state
151 | before calling `er/expand-region' for the first time."
152 | (interactive "p")
153 | (if (< arg 1)
154 | (er/contract-region (- arg))
155 | (er--prepare-expanding)
156 | (while (>= arg 1)
157 | (setq arg (- arg 1))
158 | (when (eq 'early-exit (er--expand-region-1))
159 | (setq arg 0)))
160 | (when (and expand-region-fast-keys-enabled
161 | (not (memq last-command '(er/expand-region er/contract-region))))
162 | (er/prepare-for-more-expansions))))
163 |
164 | (eval-after-load 'clojure-mode '(require 'clojure-mode-expansions))
165 | (eval-after-load 'css-mode '(require 'css-mode-expansions))
166 | (eval-after-load 'erlang-mode '(require 'erlang-mode-expansions))
167 | (eval-after-load 'feature-mode '(require 'feature-mode-expansions))
168 | (eval-after-load 'sgml-mode '(require 'html-mode-expansions)) ;; html-mode is defined in sgml-mode.el
169 | (eval-after-load 'rhtml-mode '(require 'html-mode-expansions))
170 | (eval-after-load 'nxhtml-mode '(require 'html-mode-expansions))
171 | (eval-after-load 'web-mode '(require 'web-mode-expansions))
172 | (eval-after-load 'js '(require 'js-mode-expansions))
173 | (eval-after-load 'js2-mode '(require 'js-mode-expansions))
174 | (eval-after-load 'js2-mode '(require 'js2-mode-expansions))
175 | (eval-after-load 'js3-mode '(require 'js-mode-expansions))
176 | (eval-after-load 'latex '(require 'latex-mode-expansions))
177 | (eval-after-load 'nxml-mode '(require 'nxml-mode-expansions))
178 | (eval-after-load 'octave-mod '(require 'octave-expansions))
179 | (eval-after-load 'octave '(require 'octave-expansions))
180 | (eval-after-load 'python '(progn
181 | (when expand-region-guess-python-mode
182 | (expand-region-guess-python-mode))
183 | (if (eq 'python expand-region-preferred-python-mode)
184 | (require 'python-el-expansions)
185 | (require 'python-el-fgallina-expansions))))
186 | (eval-after-load 'python-mode '(require 'python-mode-expansions))
187 | (eval-after-load 'ruby-mode '(require 'ruby-mode-expansions))
188 | (eval-after-load 'org '(require 'the-org-mode-expansions))
189 | (eval-after-load 'cc-mode '(require 'cc-mode-expansions))
190 | (eval-after-load 'text-mode '(require 'text-mode-expansions))
191 | (eval-after-load 'cperl-mode '(require 'cperl-mode-expansions))
192 | (eval-after-load 'sml-mode '(require 'sml-mode-expansions))
193 | (eval-after-load 'enh-ruby-mode '(require 'enh-ruby-mode-expansions))
194 | (eval-after-load 'subword '(require 'subword-mode-expansions))
195 | (eval-after-load 'yaml-mode '(require 'yaml-mode-expansions))
196 |
197 | (provide 'expand-region)
198 |
199 | ;;; expand-region.el ends here
200 |
--------------------------------------------------------------------------------
/er-basic-expansions.el:
--------------------------------------------------------------------------------
1 | ;;; er-basic-expansions.el --- Words, symbols, strings, et al -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2011-2023 Free Software Foundation, Inc
4 |
5 | ;; Author: Magnar Sveen
6 | ;; Keywords: marking region
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 |
23 | ;; Expansions that are useful in any major mode.
24 |
25 | ;;; Code:
26 |
27 | (require 'expand-region-core)
28 |
29 | (defun er/mark-word ()
30 | "Mark the entire word around or in front of point."
31 | (interactive)
32 | (let ((word-regexp "\\sw"))
33 | (when (or (looking-at word-regexp)
34 | (er/looking-back-on-line word-regexp))
35 | (skip-syntax-forward "w")
36 | (set-mark (point))
37 | (skip-syntax-backward "w"))))
38 |
39 | (defun er/mark-symbol ()
40 | "Mark the entire symbol around or in front of point."
41 | (interactive)
42 | (let ((symbol-regexp "\\s_\\|\\sw"))
43 | (when (or (looking-at symbol-regexp)
44 | (er/looking-back-on-line symbol-regexp))
45 | (skip-syntax-forward "_w")
46 | (set-mark (point))
47 | (skip-syntax-backward "_w"))))
48 |
49 | (defun er/mark-symbol-with-prefix ()
50 | "Mark the entire symbol around or in front of point, including prefix."
51 | (interactive)
52 | (let ((symbol-regexp "\\s_\\|\\sw")
53 | (prefix-regexp "\\s'"))
54 | (when (or (looking-at prefix-regexp)
55 | (looking-at symbol-regexp)
56 | (er/looking-back-on-line symbol-regexp))
57 | (skip-syntax-forward "'")
58 | (skip-syntax-forward "_w")
59 | (set-mark (point))
60 | (skip-syntax-backward "_w")
61 | (skip-syntax-backward "'"))))
62 |
63 | ;; Mark method call
64 |
65 | (defun er/mark-next-accessor ()
66 | "Presumes that current symbol is already marked, skips over one
67 | period and marks next symbol."
68 | (interactive)
69 | (when (use-region-p)
70 | (when (< (point) (mark))
71 | (exchange-point-and-mark))
72 | ;; (let ((symbol-regexp "\\s_\\|\\sw"))
73 | (when (looking-at "\\.")
74 | (forward-char 1)
75 | (skip-syntax-forward "_w")
76 | (exchange-point-and-mark)))) ;; )
77 |
78 | (defun er/mark-method-call ()
79 | "Mark the current symbol (including dots) and then paren to closing paren."
80 | (interactive)
81 | (let ((symbol-regexp "\\(\\s_\\|\\sw\\|\\.\\)+"))
82 | (when (or (looking-at symbol-regexp)
83 | (er/looking-back-on-line symbol-regexp))
84 | (skip-syntax-backward "_w.")
85 | (set-mark (point))
86 | (when (looking-at symbol-regexp)
87 | (goto-char (match-end 0)))
88 | (if (looking-at "(")
89 | (forward-list))
90 | (exchange-point-and-mark))))
91 |
92 | ;; Comments
93 |
94 | (defun er--point-is-in-comment-p ()
95 | "t if point is in comment, otherwise nil"
96 | (or (nth 4 (syntax-ppss))
97 | (memq (get-text-property (point) 'face) '(font-lock-comment-face font-lock-comment-delimiter-face))))
98 |
99 | (defun er/mark-comment ()
100 | "Mark the entire comment around point."
101 | (interactive)
102 | (when (er--point-is-in-comment-p)
103 | (let ((p (point)))
104 | (while (and (er--point-is-in-comment-p) (not (eobp)))
105 | (forward-char 1))
106 | (skip-chars-backward "\n\r")
107 | (set-mark (point))
108 | (goto-char p)
109 | (while (er--point-is-in-comment-p)
110 | (forward-char -1))
111 | (forward-char 1))))
112 |
113 | ;; Quotes
114 |
115 | (defun er--current-quotes-char ()
116 | "The char that is the current quote delimiter"
117 | (nth 3 (syntax-ppss)))
118 |
119 | (defalias 'er--point-inside-string-p #'er--current-quotes-char)
120 |
121 | (defun er--move-point-forward-out-of-string ()
122 | "Move point forward until it exits the current quoted string."
123 | (er--move-point-backward-out-of-string)
124 | (forward-sexp))
125 |
126 | (defun er--move-point-backward-out-of-string ()
127 | "Move point backward until it exits the current quoted string."
128 | (goto-char (nth 8 (syntax-ppss))))
129 |
130 | (defun er/mark-inside-quotes ()
131 | "Mark the inside of the current string, not including the quotation marks."
132 | (interactive)
133 | (when (er--point-inside-string-p)
134 | (er--move-point-backward-out-of-string)
135 | (forward-char)
136 | (set-mark (point))
137 | (er--move-point-forward-out-of-string)
138 | (backward-char)
139 | (exchange-point-and-mark)))
140 |
141 | (defun er/mark-outside-quotes ()
142 | "Mark the current string, including the quotation marks."
143 | (interactive)
144 | (if (er--point-inside-string-p)
145 | (er--move-point-backward-out-of-string)
146 | (when (and (not (use-region-p))
147 | (er/looking-back-on-line "\\s\""))
148 | (backward-char)
149 | (er--move-point-backward-out-of-string)))
150 | (when (looking-at "\\s\"")
151 | (set-mark (point))
152 | (forward-char)
153 | (er--move-point-forward-out-of-string)
154 | (exchange-point-and-mark)))
155 |
156 | ;; Pairs - ie [] () {} etc
157 |
158 | (defun er--point-inside-pairs-p ()
159 | "Is point inside any pairs?"
160 | (> (car (syntax-ppss)) 0))
161 |
162 | (defun er/mark-inside-pairs ()
163 | "Mark inside pairs (as defined by the mode), not including the pairs."
164 | (interactive)
165 | (when (er--point-inside-pairs-p)
166 | (goto-char (nth 1 (syntax-ppss)))
167 | (set-mark (save-excursion
168 | (forward-char 1)
169 | (skip-chars-forward er--space-str)
170 | (point)))
171 | (forward-list)
172 | (backward-char)
173 | (skip-chars-backward er--space-str)
174 | (exchange-point-and-mark)))
175 |
176 | (defun er--looking-at-pair ()
177 | "Is point looking at an opening pair char?"
178 | (looking-at "\\s("))
179 |
180 | (defun er--looking-at-marked-pair ()
181 | "Is point looking at a pair that is entirely marked?"
182 | (and (er--looking-at-pair)
183 | (use-region-p)
184 | (>= (mark)
185 | (save-excursion
186 | (forward-list)
187 | (point)))))
188 |
189 | (defun er/mark-outside-pairs ()
190 | "Mark pairs (as defined by the mode), including the pair chars."
191 | (interactive)
192 | (if (and (er/looking-back-on-line "\\s)+\\=")
193 | (not (er--looking-at-pair)))
194 | (ignore-errors (backward-list 1))
195 | (skip-chars-forward er--space-str))
196 | (when (and (er--point-inside-pairs-p)
197 | (or (not (er--looking-at-pair))
198 | (er--looking-at-marked-pair)))
199 | (goto-char (nth 1 (syntax-ppss))))
200 | (when (er--looking-at-pair)
201 | (set-mark (point))
202 | (forward-list)
203 | (exchange-point-and-mark)))
204 |
205 | (require 'thingatpt)
206 |
207 | (defun er/mark-url ()
208 | (interactive)
209 | (end-of-thing 'url)
210 | (set-mark (point))
211 | (beginning-of-thing 'url))
212 |
213 | (defun er/mark-email ()
214 | (interactive)
215 | (end-of-thing 'email)
216 | (set-mark (point))
217 | (beginning-of-thing 'email))
218 |
219 | (defun er/mark-defun ()
220 | "Mark defun around or in front of point."
221 | (interactive)
222 | (end-of-defun)
223 | (skip-chars-backward er--space-str)
224 | (set-mark (point))
225 | (beginning-of-defun)
226 | (skip-chars-forward er--space-str))
227 |
228 | ;; Methods to try expanding to
229 | (setq er/try-expand-list
230 | (append '(er/mark-word
231 | er/mark-symbol
232 | er/mark-symbol-with-prefix
233 | er/mark-next-accessor
234 | er/mark-method-call
235 | er/mark-inside-quotes
236 | er/mark-outside-quotes
237 | er/mark-inside-pairs
238 | er/mark-outside-pairs
239 | er/mark-comment
240 | er/mark-url
241 | er/mark-email
242 | er/mark-defun)
243 | er/try-expand-list))
244 |
245 | (when (and (>= emacs-major-version 29)
246 | (treesit-available-p))
247 | (defun er/mark-ts-node ()
248 | "Mark tree sitter node around or after point."
249 | (interactive)
250 | (when (treesit-language-at (point))
251 | (let* ((node (if (use-region-p)
252 | (treesit-node-on (region-beginning) (region-end))
253 | (treesit-node-at (point))))
254 | (node-start (treesit-node-start node))
255 | (node-end (treesit-node-end node)))
256 | ;; when the node fits the region exactly, try its parent node instead
257 | (when (and (= (region-beginning) node-start)
258 | (= (region-end) node-end))
259 | (when-let ((node (treesit-node-parent node)))
260 | (setq node-start (treesit-node-start node)
261 | node-end (treesit-node-end node))))
262 | (goto-char node-start)
263 | (set-mark node-end))))
264 | (setq er/try-expand-list (append er/try-expand-list '(er/mark-ts-node))))
265 |
266 | (provide 'er-basic-expansions)
267 | ;;; er-basic-expansions.el ends here
268 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://travis-ci.org/magnars/expand-region.el)
2 | [](https://coveralls.io/github/magnars/expand-region.el)
3 | [](https://elpa.gnu.org/packages/expand-region.html)
4 | [](https://melpa.org/#/expand-region)
5 | [](https://stable.melpa.org/#/expand-region)
6 |
7 | # expand-region.el
8 |
9 | Expand region increases the selected region by semantic units. Just keep
10 | pressing the key until it selects what you want.
11 |
12 | An example:
13 |
14 | (setq alphabet-start "abc def")
15 |
16 | With the cursor at the `c`, it starts by marking the entire word `abc`, then
17 | expand to the contents of the quotes `abc def`, then to the entire quote
18 | `"abc def"`, then to the contents of the sexp `setq alphabet-start "abc def"`
19 | and finally to the entire sexp.
20 |
21 | You can set it up like this:
22 |
23 | (require 'expand-region)
24 | (global-set-key (kbd "C-=") 'er/expand-region)
25 |
26 | If you expand too far, you can contract the region by pressing `-` (minus key),
27 | or by prefixing the shortcut you defined with a negative argument: `C-- C-=`.
28 |
29 | ## Maintenance warning
30 |
31 | I use this package every day, and have been doing so for years. It just works.
32 | At least, it works for all my use cases. And if it breaks somehow, I fix it.
33 |
34 | However, it has become painfully clear to me that I don't have time to fix
35 | problems I don't have. It's been years since I could keep pace with the issues
36 | and pull requests. Whenever I try, I keep getting feedback that my fix isn't
37 | good enough by some standard I don't particularly care about.
38 |
39 | So, I have closed the issue tracker and the pull requests. I hope you can
40 | happily use this package, just like I do. If it doesn't work for you, then I'm
41 | sorry. Thankfully Emacs is infinitely malleable, you can probably fix it
42 | yourself.
43 |
44 | TLDR: *I am still maintaining this package*, but I am no longer crowdsourcing a list of issues.
45 |
46 | ## Video
47 |
48 | You can [watch an intro to expand-region at Emacs Rocks](http://emacsrocks.com/e09.html).
49 |
50 | ## Installation
51 |
52 | I highly recommend installing expand-region through elpa.
53 |
54 | It's available on [MELPA](https://melpa.org/):
55 |
56 | M-x package-install expand-region
57 |
58 | Via [use-package](https://github.com/jwiegley/use-package):
59 |
60 | (use-package expand-region
61 | :bind ("C-=" . er/expand-region))
62 |
63 | ## Language support
64 |
65 | Expand region works fairly well with most languages, due to the general
66 | nature of the basic expansions:
67 |
68 | er/mark-word
69 | er/mark-symbol
70 | er/mark-symbol-with-prefix
71 | er/mark-next-accessor
72 | er/mark-method-call
73 | er/mark-inside-quotes
74 | er/mark-outside-quotes
75 | er/mark-inside-pairs
76 | er/mark-outside-pairs
77 | er/mark-comment
78 | er/mark-url
79 | er/mark-email
80 | er/mark-defun
81 |
82 | However, most languages also will benefit from some specially crafted
83 | expansions. For instance, expand-region comes with these extra expansions for
84 | html-mode:
85 |
86 | er/mark-html-attribute
87 | er/mark-inner-tag
88 | er/mark-outer-tag
89 |
90 | You can add your own expansions to the languages of your choice simply by
91 | creating a function that looks around point to see if it's inside or looking
92 | at the construct you want to mark, and if so - mark it.
93 |
94 | There's plenty of examples to look at in these files.
95 |
96 | After you make your function, add it to a buffer-local version of
97 | the `er/try-expand-list`.
98 |
99 | **Example:**
100 |
101 | Let's say you want expand-region to also mark paragraphs and pages in
102 | text-mode. Incidentally Emacs already comes with `mark-paragraph` and
103 | `mark-page`. To add it to the try-list, do this:
104 |
105 | (defun er/add-text-mode-expansions ()
106 | (make-variable-buffer-local 'er/try-expand-list)
107 | (setq er/try-expand-list (append
108 | er/try-expand-list
109 | '(mark-paragraph
110 | mark-page))))
111 |
112 | (add-hook 'text-mode-hook 'er/add-text-mode-expansions)
113 |
114 | Add that to its own file, and add it to the `expand-region.el`-file,
115 | where it says "Mode-specific expansions"
116 |
117 | **Warning:** Badly written expansions might slow down expand-region
118 | dramatically. Remember to exit quickly before you start traversing
119 | the entire document looking for constructs to mark.
120 |
121 | ## Contribute
122 |
123 | If you make some nice expansions for your favorite mode, it would be
124 | great if you opened a pull-request. The repo is at:
125 |
126 | https://github.com/magnars/expand-region.el
127 |
128 | All changes must be accompanied by feature tests.
129 | They are written in [Ecukes](http://ecukes.info), a Cucumber for Emacs.
130 |
131 | To fetch the test dependencies, install
132 | [cask](https://github.com/rejeep/cask.el) if you haven't already,
133 | then:
134 |
135 | $ cd /path/to/expand-region
136 | $ cask
137 |
138 | Run the tests with:
139 |
140 | $ ./run-tests.sh
141 |
142 | If feature tests are missing for the mode you are changing, please make
143 | sure to add a set of basic tests around the functionality you're changing.
144 |
145 | ## Contributors
146 |
147 | * [Josh Johnston](https://github.com/joshwnj) contributed `er/contract-region`
148 | * [Le Wang](https://github.com/lewang) contributed consistent handling of the mark ring, expanding into pairs/quotes just left of the cursor, and general code clean-up.
149 | * [Raimon Grau](https://github.com/kidd) added support for when transient-mark-mode is off.
150 | * [Roland Walker](https://github.com/rolandwalker) added option to copy the contents of the most recent action to a register, and some fixes.
151 | * [Damien Cassou](https://github.com/DamienCassou) added option to continue expanding/contracting with fast keys after initial expand.
152 | * [Sylvain Rousseau](https://github.com/thisirs) fixed loads of little annoyances.
153 | * [Ryan Mulligan](https://github.com/ryantm) cleaned up a lot of byte compilation warnings.
154 | * [Lefteris Karapetsas](https://github.com/LefterisJP) added subword-mode expansions.
155 |
156 | ### Language specific contributions
157 |
158 | * [Matt Briggs](https://github.com/mbriggs), [Jorge Dias](https://github.com/diasjorge) and [Le Wang](https://github.com/lewang) contributed Ruby expansions.
159 | * [Ivan Andrus](https://github.com/gvol), [fgeller](https://github.com/fgeller), [edmccard](https://github.com/edmccard) and [Rotem Yaari](https://github.com/vmalloc) contributed Python expansions.
160 | * [François Févotte](https://github.com/ffevotte) contributed C and C++ expansions.
161 | * [Ivan Andrus](https://github.com/gvol) contributed text-mode, LaTeX-mode and nxml-mode expansions.
162 | * [Gleb Peregud](https://github.com/gleber) contributed Erlang expansions.
163 | * [Mark Hepburn](https://github.com/markhepburn) contributed Octave expansions.
164 | * [Rotem Yaari](https://github.com/vmalloc) also contributed an adapter for the region expansion in web-mode.
165 | * [Kang-min Liu](https://github.com/gugod) contributed Perl expansions.
166 | * [Alexis Gallagher](https://github.com/algal) contributs Standard ML expansions.
167 | * [Matt Price](https://github.com/titaniumbones) improved on org-mode expansions.
168 | * [Maksim Grinman](https://github.com/maksle) added inner-quotes expansion for nxml-mode.
169 | * [Andrea Orru](https://github.com/AndreaOrru) added `expand-region-smart-cursor`.
170 |
171 | Thanks!
172 |
173 | ## Changelog
174 |
175 | ### From 0.11 to 0.12 (WIP)
176 |
177 | * Option `expand-region-subword-enabled` to enable subword expansions
178 | * Improve web-mode expansions (Renato F)
179 | * Fixes for cc-mode expansions (Wilfred Hughes)
180 | * Fixes for org-mode expansions (Wilfred Hughes)
181 | * Fix unnecessary unfolding in org-mode
182 | * Fix bug with transient-mark-mode (Russell Black)
183 | * Fix problems with auto-loading (Philippe Vaucher, Wilfred Hughes)
184 |
185 | ### From 0.10 to 0.11
186 |
187 | * Option `expand-region-smart-cursor` to keep cursor at beginning of region if it is there (Andrea Orru)
188 | * Add subword-mode expansions (Lefteris Karapetsas)
189 | * Improve enh-ruby-mode expansions (Ryan Davis)
190 | * Improve nxml-mode expansions (Maksim Grinman)
191 | * Improve org-mode expansions (Matt Price)
192 | * Improve js-mode expansions
193 | * Better performance
194 | * Lots of bugfixes
195 |
196 | ### From 0.9 to 0.10
197 |
198 | * Smarter expansion of ruby heredoc contents (Steve Purcell)
199 | * Add enh-ruby-mode expansions (Bradley Wright)
200 | * Add basic expansion er/mark-defun
201 | * Big cleanup of byte compilation warnings (Ryan Mulligan)
202 | * Better performance
203 | * Lots of bugfixes
204 |
205 | ### From 0.8 to 0.9
206 |
207 | * Improve org-, clojure-, python-, latex-, cc- and ruby-modes
208 | * Add basic expansions: email and url
209 | * Add sml-mode expansions (Alexis Gallagher)
210 | * Add cperl-mode expansions (Kang-min Liu)
211 | * Add octave-mode expansions (Mark Hepburn)
212 | * Add web-mode expansions (Rotem Yaari)
213 | * Use Carton for dev-dependencies
214 | * Fix bad behavior in minibuffer (Sylvain Rousseau)
215 | * More robust comment expansions
216 | * Improve loading of expansions for all major modes
217 |
218 | ### From 0.7 to 0.8
219 |
220 | * Improve js-, ruby-, python- and latex-modes
221 | * Support built-in javascript-mode
222 | * Handle narrowed buffers correctly
223 | * Include mode-specific expansions when autoloading
224 | * Provide option to copy the contents of the most recent action to a register
225 | * Add cc-mode specific expansions
226 | * Add customization to turn off skipping whitespace when expanding
227 | * Continue expanding/contracting with one key press (optional)
228 |
229 | ## License
230 |
231 | Copyright (C) 2011-2019 Magnar Sveen
232 |
233 | Author: Magnar Sveen
234 | Keywords: marking region
235 |
236 | This program is free software; you can redistribute it and/or modify
237 | it under the terms of the GNU General Public License as published by
238 | the Free Software Foundation, either version 3 of the License, or
239 | (at your option) any later version.
240 |
241 | This program is distributed in the hope that it will be useful,
242 | but WITHOUT ANY WARRANTY; without even the implied warranty of
243 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
244 | GNU General Public License for more details.
245 |
246 | You should have received a copy of the GNU General Public License
247 | along with this program. If not, see .
248 |
--------------------------------------------------------------------------------
/features/expand-region.feature:
--------------------------------------------------------------------------------
1 | Feature: Expand Region
2 | In order to quickly and precisely mark units
3 | As an Emacs user
4 | I want to expand to them
5 |
6 | Scenario: Mark entire word with point midword
7 | Given there is no region selected
8 | When I insert "This is some text"
9 | And I go to point "10"
10 | And I press "C-@"
11 | Then the region should be "some"
12 |
13 | Scenario: Mark entire word with point midword, smart cursor
14 | Given there is no region selected
15 | And cursor behaviour is set to smart
16 | When I insert "This is some text"
17 | And I go to point "10"
18 | And I press "C-@"
19 | Then the region should be "some"
20 | And cursor should be at point "13"
21 |
22 | Scenario: Mark entire word with point at beginning of word, smart cursor
23 | Given there is no region selected
24 | And cursor behaviour is set to smart
25 | When I insert "This is some text"
26 | And I go to point "9"
27 | And I press "C-@"
28 | Then the region should be "some"
29 | And cursor should be at point "9"
30 |
31 | Scenario: Mark word just behind point
32 | Given there is no region selected
33 | When I insert "This is some text"
34 | And I go to point "13"
35 | And I press "C-@"
36 | Then the region should be "some"
37 |
38 | Scenario: Multiple expand-region
39 | Given there is no region selected
40 | When I insert "This (is some) text"
41 | And I go to point "10"
42 | And I press "C-@"
43 | And I press "C-@"
44 | And I press "C-@"
45 | Then the region should be "(is some)"
46 |
47 | Scenario: Expand from existing selection
48 | Given there is no region selected
49 | When I insert "This (is some) text"
50 | And I go to point "7"
51 | And I set the mark
52 | And I go to point "14"
53 | And I press "C-@"
54 | Then the region should be "(is some)"
55 |
56 | Scenario: Skip white space forward if spaces on both sides of cursor
57 | Given there is no region selected
58 | When I insert "This is some text"
59 | And I go to point "10"
60 | And I press "C-@"
61 | Then the region should be "some"
62 |
63 | Scenario: Skip white space forward if at beginning of buffer
64 | Given there is no region selected
65 | When I insert " This is some text"
66 | And I go to beginning of buffer
67 | And I press "C-@"
68 | Then the region should be "This"
69 |
70 | Scenario: Skip white space forward if at beginning of line
71 | Given there is no region selected
72 | When I insert:
73 | """
74 | This is
75 | some text
76 | """
77 | And I go to point "9"
78 | And I press "C-@"
79 | Then the region should be "some"
80 |
81 | Scenario: Do not skip white space forward with active region
82 | Given there is no region selected
83 | When I insert "This is some text"
84 | And I go to point "10"
85 | And I set the mark
86 | And I go to point "14"
87 | And I press "C-@"
88 | Then the region should be "This is some text"
89 |
90 | Scenario: Contract region once
91 | Given there is no region selected
92 | When I insert "(((45678)))"
93 | And I go to point "6"
94 | And I press "C-@"
95 | And I press "C-@"
96 | And I press "C-@"
97 | And I press "C-S-@"
98 | Then the region should be "(45678)"
99 |
100 | Scenario: Contract region twice
101 | Given there is no region selected
102 | When I insert "(((45678)))"
103 | And I go to point "6"
104 | And I press "C-@"
105 | And I press "C-@"
106 | And I press "C-@"
107 | And I press "C-S-@"
108 | And I press "C-S-@"
109 | Then the region should be "45678"
110 |
111 | Scenario: Contract region twice, smart cursor, beginning of word
112 | Given there is no region selected
113 | And cursor behaviour is set to smart
114 | When I insert "(((45678)))"
115 | And I go to point "4"
116 | And I press "C-@"
117 | And I press "C-@"
118 | And I press "C-@"
119 | And I press "C-S-@"
120 | And I press "C-S-@"
121 | Then the region should be "45678"
122 | And cursor should be at point "4"
123 |
124 | Scenario: Contract region twice, smart cursor, midword
125 | Given there is no region selected
126 | And cursor behaviour is set to smart
127 | When I insert "(((45678)))"
128 | And I go to point "6"
129 | And I press "C-@"
130 | And I press "C-@"
131 | And I press "C-@"
132 | And I press "C-S-@"
133 | And I press "C-S-@"
134 | Then the region should be "45678"
135 | And cursor should be at point "9"
136 |
137 | Scenario: Contract region all the way back to start
138 | Given there is no region selected
139 | When I insert "(((45678)))"
140 | And I go to point "6"
141 | And I press "C-@"
142 | And I press "C-@"
143 | And I press "C-@"
144 | And I press "C-S-@"
145 | And I press "C-S-@"
146 | And I press "C-S-@"
147 | Then the region should not be active
148 | And cursor should be at point "6"
149 |
150 | Scenario: Contract region should only contract previous expansions
151 | Given there is no region selected
152 | When I insert "This (is some) text"
153 | And I go to point "7"
154 | And I set the mark
155 | And I go to point "14"
156 | And I press "C-S-@"
157 | Then the region should be "is some"
158 |
159 | Scenario: Contract history should be reset when changing buffer
160 | Given there is no region selected
161 | When I insert "This is some text"
162 | And I go to point "10"
163 | And I press "C-@"
164 | And I press "C-@"
165 | And I deactivate the mark
166 | And I insert "More text"
167 | And I press "C-S-@"
168 | Then the region should not be active
169 |
170 | Scenario: Expanding past the entire buffer should not add duplicates to the history
171 | Given there is no region selected
172 | When I insert "This is some text"
173 | And I press "C-@"
174 | And I press "C-@"
175 | And I press "C-@"
176 | And I press "C-@"
177 | And I press "C-@"
178 | And I press "C-S-@"
179 | Then the region should be "text"
180 |
181 | Scenario: C-g to deactivate mark and move back to start of expansions
182 | Given there is no region selected
183 | When I insert "(((45678)))"
184 | And I go to point "6"
185 | And I press "C-@"
186 | And I press "C-@"
187 | And I quit
188 | Then the region should not be active
189 | And cursor should be at point "6"
190 |
191 | Scenario: C-g to move back to start of expansions also with cua-mode
192 | Given there is no region selected
193 | When I turn on cua-mode
194 | And I insert "(((45678)))"
195 | And I go to point "6"
196 | And I press "C-@"
197 | And I press "C-@"
198 | And I quit
199 | Then the region should not be active
200 | And cursor should be at point "6"
201 |
202 | Scenario: Pop mark twice to get back to start of expansions
203 | Given there is no region selected
204 | When I insert "(((45678)))"
205 | And I go to point "6"
206 | And I press "C-@"
207 | And I press "C-@"
208 | And I press "C-S-@"
209 | And I press "C-@"
210 | And I press "C-@"
211 | And I press "C-@"
212 | And I press "C-@"
213 | And I press "C-S-@"
214 | And I press "C-@"
215 | And I press "C-@"
216 | And I pop the mark
217 | And I pop the mark
218 | Then cursor should be at point "6"
219 |
220 | Scenario: Pop mark thrice to get back to mark before expansions
221 | Given there is no region selected
222 | When I insert "(((45678)))"
223 | And I go to point "8"
224 | And I set the mark
225 | And I deactivate the mark
226 | And I go to point "6"
227 | And I press "C-@"
228 | And I press "C-@"
229 | And I press "C-S-@"
230 | And I press "C-@"
231 | And I press "C-@"
232 | And I press "C-@"
233 | And I press "C-@"
234 | And I press "C-S-@"
235 | And I press "C-@"
236 | And I press "C-@"
237 | And I pop the mark
238 | And I pop the mark
239 | And I pop the mark
240 | Then cursor should be at point "8"
241 |
242 | Scenario: Transient mark mode deactivated
243 | Given transient mark mode is inactive
244 | And there is no region selected
245 | When I insert "This is some text"
246 | And I go to point "10"
247 | And I press "C-@"
248 | Then the region should be "some"
249 |
250 | Scenario: Expand from existing selection without transient-mark-mode
251 | Given transient mark mode is inactive
252 | And there is no region selected
253 | When I insert "This (is some) text"
254 | And I go to point "7"
255 | And I set the mark
256 | And I activate the mark
257 | And I go to point "14"
258 | And I press "C-@"
259 | Then the region should be "(is some)"
260 |
261 | Scenario: Do not skip white space forward with active region without tmm
262 | Given transient mark mode is inactive
263 | And there is no region selected
264 | When I insert "This is some text"
265 | And I go to point "10"
266 | And I set the mark
267 | And I activate the mark
268 | And I go to point "14"
269 | And I press "C-@"
270 | Then the region should be "This is some text"
271 |
272 | Scenario: Set-mark-default-inactive
273 | Given mark is inactive by default
274 | And there is no region selected
275 | When I insert "This (is some) text"
276 | And I go to point "6"
277 | And I press "C-@"
278 | Then the region should be "(is some)"
279 |
280 | Scenario: Allow pressing the last key of the sequence continuously
281 | Given there is no region selected
282 | When I insert "This (is (some)) text"
283 | And I go to point "12"
284 | And I press "C-@"
285 | Then the region should be "some"
286 | And I press "@"
287 | Then the region should be "(some)"
288 | And I press "@"
289 | Then the region should be "is (some)"
290 | And I press "@"
291 | Then the region should be "(is (some))"
292 |
293 | Scenario: Allow pressing `-' to contract region
294 | Given there is no region selected
295 | When I insert "This (is (some)) text"
296 | And I go to point "12"
297 | And I press "C-@"
298 | Then the region should be "some"
299 | And I press "@"
300 | Then the region should be "(some)"
301 | And I press "@"
302 | Then the region should be "is (some)"
303 | And I press "-"
304 | Then the region should be "(some)"
305 | And I press "-"
306 | Then the region should be "some"
307 |
308 | Scenario: Allow pressing `0' to reset region
309 | Given there is no region selected
310 | When I insert "This (is (some)) text"
311 | And I go to point "12"
312 | And I press "C-@"
313 | Then the region should be "some"
314 | And I press "@"
315 | Then the region should be "(some)"
316 | And I press "@"
317 | Then the region should be "is (some)"
318 | And I press "0"
319 | Then there is no region selected
320 | And cursor should be at point "12"
321 |
322 | Scenario: Allow pressing C-g to reset region after pressing `@'
323 | Given there is no region selected
324 | When I insert "This (is (some)) text"
325 | And I go to point "12"
326 | And I press "C-@"
327 | Then the region should be "some"
328 | And I press "@"
329 | Then the region should be "(some)"
330 | And I press "@"
331 | Then the region should be "is (some)"
332 | And I quit
333 | Then there is no region selected
334 | And cursor should be at point "12"
335 |
336 | Scenario: Allow pressing C-g to reset region after pressing `-'
337 | Given there is no region selected
338 | When I insert "This (is (some)) text"
339 | And I go to point "12"
340 | And I press "C-@"
341 | Then the region should be "some"
342 | And I press "@"
343 | Then the region should be "(some)"
344 | And I press "-"
345 | Then the region should be "some"
346 | And I quit
347 | Then there is no region selected
348 | And cursor should be at point "12"
349 |
350 | Scenario: Autocopy-register
351 | Given there is no region selected
352 | And autocopy-register is "e"
353 | When I insert "This is some text"
354 | And I go to point "10"
355 | And I press "C-@"
356 | Then register "e" should be "some"
357 |
--------------------------------------------------------------------------------
/expand-region-core.el:
--------------------------------------------------------------------------------
1 | ;;; expand-region-core.el --- Increase selected region by semantic units. -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2011-2023 Free Software Foundation, Inc
4 |
5 | ;; Author: Magnar Sveen
6 | ;; Keywords: marking region
7 |
8 | ;; This program is free software; you can redistribute it and/or modify
9 | ;; it under the terms of the GNU General Public License as published by
10 | ;; the Free Software Foundation, either version 3 of the License, or
11 | ;; (at your option) any later version.
12 |
13 | ;; This program is distributed in the hope that it will be useful,
14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | ;; GNU General Public License for more details.
17 |
18 | ;; You should have received a copy of the GNU General Public License
19 | ;; along with this program. If not, see .
20 |
21 | ;;; Commentary:
22 |
23 | ;; The core functionality of expand-region.
24 |
25 | ;; See README.md
26 |
27 | ;;; Code:
28 |
29 | (require 'expand-region-custom)
30 | (declare-function er/expand-region "expand-region")
31 |
32 | (defvar er/history '()
33 | "A history of start and end points so we can contract after expanding.")
34 |
35 | ;; history is always local to a single buffer
36 | (make-variable-buffer-local 'er/history)
37 |
38 | (defvar er--space-str " \t\n")
39 | (defvar er--blank-list (append er--space-str nil))
40 |
41 | (defvar er--show-expansion-message nil)
42 |
43 | (defvar er/try-expand-list nil
44 | "A list of functions that are tried when expanding.")
45 |
46 | (defvar er/save-mode-excursion nil
47 | "A function to save excursion state when expanding.")
48 |
49 | (defsubst er--first-invocation ()
50 | "t if this is the first invocation of `er/expand-region' or `er/contract-region'."
51 | (not (memq last-command '(er/expand-region er/contract-region))))
52 |
53 | (defun er--prepare-expanding ()
54 | (when (and (er--first-invocation)
55 | (not (use-region-p)))
56 | (push-mark nil t) ;; one for keeping starting position
57 | (push-mark nil t)) ;; one for replace by set-mark in expansions
58 |
59 | (when (not transient-mark-mode)
60 | (setq-local transient-mark-mode (cons 'only transient-mark-mode))))
61 |
62 | (defun er--copy-region-to-register ()
63 | (when (and (stringp expand-region-autocopy-register)
64 | (> (length expand-region-autocopy-register) 0))
65 | (set-register (aref expand-region-autocopy-register 0)
66 | (filter-buffer-substring (region-beginning) (region-end)))))
67 |
68 | ;; save-mark-and-excursion in Emacs 25 works like save-excursion did before
69 | (eval-when-compile
70 | (when (< emacs-major-version 25)
71 | (defmacro save-mark-and-excursion (&rest body)
72 | `(save-excursion ,@body))))
73 |
74 | (defmacro er--save-excursion (&rest body)
75 | `(let ((action (lambda ()
76 | (save-mark-and-excursion ,@body))))
77 | (if er/save-mode-excursion
78 | (funcall er/save-mode-excursion action)
79 | (funcall action))))
80 |
81 | (defun er--expand-region-1 ()
82 | "Increase selected region by semantic units.
83 | Basically it runs all the mark-functions in `er/try-expand-list'
84 | and chooses the one that increases the size of the region while
85 | moving point or mark as little as possible."
86 | (let* ((p1 (point))
87 | (p2 (if (use-region-p) (mark) (point)))
88 | (start (min p1 p2))
89 | (end (max p1 p2))
90 | (try-list er/try-expand-list)
91 | (best-start (point-min))
92 | (best-end (point-max))
93 | ;; (set-mark-default-inactive nil)
94 | )
95 |
96 | ;; add hook to clear history on buffer changes
97 | (unless er/history
98 | (add-hook 'after-change-functions #'er/clear-history t t))
99 |
100 | ;; remember the start and end points so we can contract later
101 | ;; unless we're already at maximum size
102 | (unless (and (= start best-start)
103 | (= end best-end))
104 | (push (cons p1 p2) er/history))
105 |
106 | (when (and expand-region-skip-whitespace
107 | (er--point-is-surrounded-by-white-space)
108 | (= start end))
109 | (skip-chars-forward er--space-str)
110 | (setq start (point)))
111 |
112 | (while try-list
113 | (er--save-excursion
114 | (ignore-errors
115 | (funcall (car try-list))
116 | (when (and (region-active-p)
117 | (er--this-expansion-is-better start end best-start best-end))
118 | (setq best-start (point))
119 | (setq best-end (mark))
120 | (when (and er--show-expansion-message (not (minibufferp)))
121 | (message "%S" (car try-list))))))
122 | (setq try-list (cdr try-list)))
123 |
124 | (setq deactivate-mark nil)
125 | ;; if smart cursor enabled, decide to put it at start or end of region:
126 | (if (and expand-region-smart-cursor
127 | (not (= start best-start)))
128 | (progn (goto-char best-end)
129 | (set-mark best-start))
130 | (goto-char best-start)
131 | (set-mark best-end))
132 |
133 | (er--copy-region-to-register)
134 |
135 | (when (and (= best-start (point-min))
136 | (= best-end (point-max))) ;; We didn't find anything new, so exit early
137 | 'early-exit)))
138 |
139 | (defun er--this-expansion-is-better (start end best-start best-end)
140 | "t if the current region is an improvement on previous expansions.
141 |
142 | This is provided as a separate function for those that would like
143 | to override the heuristic."
144 | (and
145 | (<= (point) start)
146 | (>= (mark) end)
147 | (> (- (mark) (point)) (- end start))
148 | (or (> (point) best-start)
149 | (and (= (point) best-start)
150 | (< (mark) best-end)))))
151 |
152 | ;;;###autoload
153 | (defun er/contract-region (arg)
154 | "Contract the selected region to its previous size.
155 | With prefix argument contracts that many times.
156 | If prefix argument is negative calls `er/expand-region'.
157 | If prefix argument is 0 it resets point and mark to their state
158 | before calling `er/expand-region' for the first time."
159 | (interactive "p")
160 | (if (< arg 0)
161 | (er/expand-region (- arg))
162 | (when er/history
163 | ;; Be sure to reset them all if called with 0
164 | (when (= arg 0)
165 | (setq arg (length er/history)))
166 |
167 | (when (not transient-mark-mode)
168 | (setq-local transient-mark-mode (cons 'only transient-mark-mode)))
169 |
170 | ;; Advance through the list the desired distance
171 | (while (and (cdr er/history)
172 | (> arg 1))
173 | (setq arg (- arg 1))
174 | (setq er/history (cdr er/history)))
175 | ;; Reset point and mark
176 | (let* ((last (pop er/history))
177 | (start (car last))
178 | (end (cdr last)))
179 | (goto-char start)
180 | (set-mark end)
181 |
182 | (er--copy-region-to-register)
183 |
184 | (when (eq start end)
185 | (deactivate-mark)
186 | (er/clear-history))))))
187 |
188 | (defun er/prepare-for-more-expansions-internal (repeat-key-str)
189 | "Return bindings and a message to inform user about them"
190 | (let ((msg (format "Type %s to expand again" repeat-key-str))
191 | (bindings (list (cons repeat-key-str '(er/expand-region 1)))))
192 | ;; If contract and expand are on the same binding, ignore contract
193 | (unless (string-equal repeat-key-str expand-region-contract-fast-key)
194 | (setq msg (concat msg (format ", %s to contract" expand-region-contract-fast-key)))
195 | (push (cons expand-region-contract-fast-key '(er/contract-region 1)) bindings))
196 | ;; If reset and either expand or contract are on the same binding, ignore reset
197 | (unless (or (string-equal repeat-key-str expand-region-reset-fast-key)
198 | (string-equal expand-region-contract-fast-key expand-region-reset-fast-key))
199 | (setq msg (concat msg (format ", %s to reset" expand-region-reset-fast-key)))
200 | (push (cons expand-region-reset-fast-key '(er/expand-region 0)) bindings))
201 | (cons msg bindings)))
202 |
203 | (defun er/prepare-for-more-expansions ()
204 | "Let one expand more by just pressing the last key."
205 | (let* ((repeat-key (event-basic-type last-input-event))
206 | (repeat-key-str (single-key-description repeat-key))
207 | (msg-and-bindings (er/prepare-for-more-expansions-internal repeat-key-str))
208 | (msg (car msg-and-bindings))
209 | (bindings (cdr msg-and-bindings)))
210 | (when repeat-key
211 | (er/set-temporary-overlay-map
212 | (let ((map (make-sparse-keymap)))
213 | (dolist (binding bindings map)
214 | (define-key map (read-kbd-macro (car binding))
215 | `(lambda ()
216 | (interactive)
217 | (setq this-command `,(cadr ',binding))
218 | (or (not expand-region-show-usage-message) (minibufferp) (message "%s" ,msg))
219 | (eval `,(cdr ',binding))))))
220 | t)
221 | (or (not expand-region-show-usage-message) (minibufferp) (message "%s" msg)))))
222 |
223 | (defalias 'er/set-temporary-overlay-map
224 | (if (fboundp 'set-temporary-overlay-map) ;Emacs≥24.3
225 | #'set-temporary-overlay-map
226 | ;; Backport this function from newer emacs versions
227 | (lambda (map &optional keep-pred)
228 | "Set a new keymap that will only exist for a short period of time.
229 | The new keymap to use must be given in the MAP variable. When to
230 | remove the keymap depends on user input and KEEP-PRED:
231 |
232 | - if KEEP-PRED is nil (the default), the keymap disappears as
233 | soon as any key is pressed, whether or not the key is in MAP;
234 |
235 | - if KEEP-PRED is t, the keymap disappears as soon as a key *not*
236 | in MAP is pressed;
237 |
238 | - otherwise, KEEP-PRED must be a 0-arguments predicate that will
239 | decide if the keymap should be removed (if predicate returns
240 | nil) or kept (otherwise). The predicate will be called after
241 | each key sequence."
242 |
243 | (let* ((clearfunsym (make-symbol "clear-temporary-overlay-map"))
244 | (overlaysym (make-symbol "t"))
245 | (alist (list (cons overlaysym map)))
246 | (clearfun
247 | `(lambda ()
248 | (unless ,(cond ((null keep-pred) nil)
249 | ((eq t keep-pred)
250 | `(eq this-command
251 | (lookup-key ',map
252 | (this-command-keys-vector))))
253 | (t `(funcall ',keep-pred)))
254 | (remove-hook 'pre-command-hook ',clearfunsym)
255 | (setq emulation-mode-map-alists
256 | (delq ',alist emulation-mode-map-alists))))))
257 | (set overlaysym overlaysym)
258 | (fset clearfunsym clearfun)
259 | (add-hook 'pre-command-hook clearfunsym)
260 |
261 | (push alist emulation-mode-map-alists)))))
262 |
263 | (advice-add 'keyboard-quit :before #'er--collapse-region-before)
264 | (advice-add 'cua-cancel :before #'er--collapse-region-before)
265 | (defun er--collapse-region-before (&rest _)
266 | ;; FIXME: Re-use `er--first-invocation'?
267 | (when (memq last-command '(er/expand-region er/contract-region))
268 | (er/contract-region 0)))
269 |
270 | (advice-add 'minibuffer-keyboard-quit
271 | :around #'er--collapse-region-minibuffer-keyboard-quit)
272 | (defun er--collapse-region-minibuffer-keyboard-quit (orig-fun &rest args)
273 | ;; FIXME: Re-use `er--first-invocation'?
274 | (if (memq last-command '(er/expand-region er/contract-region))
275 | (er/contract-region 0)
276 | (apply orig-fun args)))
277 |
278 |
279 | (defun er/clear-history (&rest _)
280 | "Clear the history."
281 | (setq er/history '())
282 | (remove-hook 'after-change-functions #'er/clear-history t))
283 |
284 | (defun er--point-is-surrounded-by-white-space ()
285 | (and (or (memq (char-before) er--blank-list)
286 | (eq (point) (point-min)))
287 | (memq (char-after) er--blank-list)))
288 |
289 | (defun er/enable-mode-expansions (mode add-fn)
290 | (add-hook (intern (format "%s-hook" mode)) add-fn)
291 | (save-window-excursion ;; FIXME: Why?
292 | (dolist (buffer (buffer-list))
293 | (with-current-buffer buffer
294 | (when (derived-mode-p mode)
295 | (funcall add-fn))))))
296 |
297 | (defun er/enable-minor-mode-expansions (mode add-fn)
298 | (add-hook (intern (format "%s-hook" mode)) add-fn)
299 | (save-window-excursion
300 | (dolist (buffer (buffer-list))
301 | (with-current-buffer buffer
302 | (when (symbol-value mode)
303 | (funcall add-fn))))))
304 |
305 | ;; Some more performant version of `looking-back'
306 |
307 | (defun er/looking-back-on-line (regexp)
308 | "Version of `looking-back' that only checks current line."
309 | (looking-back regexp (line-beginning-position)))
310 |
311 | (defun er/looking-back-exact (s)
312 | "Version of `looking-back' that only looks for exact matches, no regexp."
313 | (string= s (buffer-substring (- (point) (length s))
314 | (point))))
315 |
316 | (defun er/looking-back-max (regexp count)
317 | "Version of `looking-back' that only check COUNT chars back."
318 | (looking-back regexp (max 1 (- (point) count))))
319 |
320 | (provide 'expand-region-core)
321 |
322 | ;;; expand-region-core.el ends here
323 |
--------------------------------------------------------------------------------