├── features ├── .nosearch ├── latex-mode-expansions.feature ├── support │ └── env.el ├── org-mode-expansions.feature ├── c++-mode-expansions.feature ├── cperl-mode-expansions.feature ├── er-basic-expansions.feature ├── octave-mode-expansions.feature ├── mark-pairs.feature ├── step-definitions │ └── expand-region-steps.el ├── text-mode-expansions.feature ├── html-mode-expansions.feature ├── c-mode-expansions.feature ├── nxml-mode-expansions.feature ├── fgallina-python-el-expansions.feature ├── ruby-mode-expansions.feature └── expand-region.feature ├── run-tests.sh ├── .gitignore ├── Cask ├── run-travis-ci.sh ├── .travis.yml ├── watch-tests.watchr ├── web-mode-expansions.el ├── enh-ruby-mode-expansions.el ├── erlang-mode-expansions.el ├── subword-mode-expansions.el ├── css-mode-expansions.el ├── sml-mode-expansions.el ├── js2-mode-expansions.el ├── jsp-expansions.el ├── text-mode-expansions.el ├── cperl-mode-expansions.el ├── feature-mode-expansions.el ├── octave-expansions.el ├── python-el-expansions.el ├── html-mode-expansions.el ├── latex-mode-expansions.el ├── the-org-mode-expansions.el ├── clojure-mode-expansions.el ├── expand-region-custom.el ├── nxml-mode-expansions.el ├── python-mode-expansions.el ├── js-mode-expansions.el ├── cc-mode-expansions.el ├── ruby-mode-expansions.el ├── python-el-fgallina-expansions.el ├── yaml-mode-expansions.el ├── expand-region.el ├── er-basic-expansions.el ├── README.md └── expand-region-core.el /features/.nosearch: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | cask exec ecukes "$@" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | expand-region-autoloads.el 2 | expand-region-pkg.el 3 | *.elc 4 | .rvmrc 5 | /TAGS 6 | elpa 7 | -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source melpa) 2 | 3 | (package "expand-region" "0.8.0" "Increase selected region by semantic units.") 4 | 5 | (development 6 | (depends-on "ecukes") 7 | (depends-on "espuds") 8 | (depends-on "undercover")) 9 | -------------------------------------------------------------------------------- /run-travis-ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | cd "$(dirname "$0")" 4 | 5 | ECUKES_EMACS=${EMACS:-$(which emacs)} 6 | export ECUKES_EMACS 7 | 8 | echo "*** Emacs version ***" 9 | echo "ECUKES_EMACS = $ECUKES_EMACS" 10 | "$ECUKES_EMACS" --version 11 | echo 12 | 13 | exec ./run-tests.sh $TAGS 14 | -------------------------------------------------------------------------------- /features/latex-mode-expansions.feature: -------------------------------------------------------------------------------- 1 | Feature: latex-mode expansions 2 | Background: 3 | Given there is no region selected 4 | And I turn on latex-mode 5 | 6 | Scenario: Mark simple math 7 | When I insert "$E=mc^2$" 8 | And I place the cursor before "=" 9 | And I press "C-@" 10 | Then the region should be "E" 11 | And I press "C-@" 12 | Then the region should be "E=mc" 13 | And I press "C-@" 14 | Then the region should be "$E=mc^2$" 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: emacs-lisp 2 | before_install: 3 | - curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw > x.sh && source ./x.sh 4 | - evm install $EVM_EMACS --use --skip 5 | - cask 6 | env: 7 | - EVM_EMACS=emacs-24.4-travis 8 | - EVM_EMACS=emacs-24.5-travis 9 | - EVM_EMACS=emacs-25.1-travis 10 | - EVM_EMACS=emacs-git-snapshot-travis 11 | script: 12 | ./run-travis-ci.sh 13 | 14 | matrix: 15 | allow_failures: 16 | - env: EVM_EMACS=emacs-git-snapshot-travis 17 | -------------------------------------------------------------------------------- /watch-tests.watchr: -------------------------------------------------------------------------------- 1 | ENV["WATCHR"] = "1" 2 | system 'clear' 3 | 4 | def run(cmd) 5 | `#{cmd}` 6 | end 7 | 8 | def run_all_tests 9 | system('clear') 10 | result = run "./run-tests.sh" 11 | puts result 12 | end 13 | 14 | def run_test(file) 15 | system('clear') 16 | result = run "./run-tests.sh #{file} --verbose" 17 | puts result 18 | end 19 | 20 | run_all_tests 21 | watch('.*.feature') { |file| run_test file } 22 | watch('.*.el') { run_all_tests } 23 | 24 | # Ctrl-\ 25 | Signal.trap 'QUIT' do 26 | puts " --- Running all tests ---\n\n" 27 | run_all_tests 28 | end 29 | 30 | @interrupted = false 31 | 32 | # Ctrl-C 33 | Signal.trap 'INT' do 34 | if @interrupted then 35 | @wants_to_quit = true 36 | abort("\n") 37 | else 38 | puts "Interrupt a second time to quit" 39 | @interrupted = true 40 | Kernel.sleep 1.5 41 | # raise Interrupt, nil # let the run loop catch it 42 | run_all_tests 43 | @interrupted = false 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /features/support/env.el: -------------------------------------------------------------------------------- 1 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc -*- lexical-binding: t; -*- 2 | 3 | (let* ((current-directory (file-name-directory load-file-name)) 4 | (features-directory (expand-file-name ".." current-directory)) 5 | (project-directory (expand-file-name ".." features-directory))) 6 | (setq expand-region-root-path project-directory)) 7 | 8 | (add-to-list 'load-path expand-region-root-path) 9 | 10 | (require 'undercover) 11 | (undercover "*.el") 12 | 13 | (require 'expand-region) 14 | (require 'espuds) 15 | (require 'ert) 16 | 17 | (Before 18 | (global-set-key (kbd "C-@") 'er/expand-region) 19 | (global-set-key (kbd "C-S-@") 'er/contract-region) 20 | (switch-to-buffer 21 | (get-buffer-create "*expand-region*")) 22 | (erase-buffer) 23 | (fundamental-mode) 24 | (transient-mark-mode 1) 25 | (cua-mode 0) 26 | (setq er--show-expansion-message t) 27 | (setq expand-region-smart-cursor nil) 28 | (setq set-mark-default-inactive nil) 29 | (deactivate-mark)) 30 | 31 | (After) 32 | ;; Local Variables: 33 | ;; no-byte-compile: t 34 | ;; End: 35 | -------------------------------------------------------------------------------- /features/org-mode-expansions.feature: -------------------------------------------------------------------------------- 1 | Feature: org-mode expansions 2 | In order to quickly and precisely mark org mode sections 3 | As an Emacs user 4 | I want to expand to them 5 | 6 | Scenario: Org level 3 7 | Given I turn on org-mode 8 | When I insert: 9 | """ 10 | * lvl 1 11 | ** lvl 2 12 | *** lvl 3 13 | """ 14 | And I place the cursor before "*** lvl 3" 15 | And I press "C-@" 16 | And I press "C-@" 17 | Then the region should be "*** lvl 3" 18 | 19 | Scenario: Org level 2 20 | Given I turn on org-mode 21 | When I insert: 22 | """ 23 | * lvl 1 24 | ** lvl 2 25 | *** lvl 3 26 | """ 27 | And I place the cursor before "*** lvl 3" 28 | And I press "C-@" 29 | And I press "C-@" 30 | And I press "C-@" 31 | Then the region should be: 32 | """ 33 | ** lvl 2 34 | *** lvl 3 35 | """ 36 | 37 | Scenario: Org level 1 38 | Given I turn on org-mode 39 | When I insert: 40 | """ 41 | * lvl 1 42 | ** lvl 2 43 | *** lvl 3 44 | """ 45 | And I place the cursor before "*** lvl 3" 46 | And I press "C-@" 47 | And I press "C-@" 48 | And I press "C-@" 49 | And I press "C-@" 50 | Then the region should be: 51 | """ 52 | * lvl 1 53 | ** lvl 2 54 | *** lvl 3 55 | """ 56 | -------------------------------------------------------------------------------- /web-mode-expansions.el: -------------------------------------------------------------------------------- 1 | ;;; web-mode-expansions.el --- Thin layer for adapting fxbois's web-mode-mark-and-expand function -*- lexical-binding: t; -*- 2 | ;;; to expand-region 3 | 4 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc 5 | 6 | ;; Authors: Rotem Yaari 7 | ;; Based on, and makes use of web-mode.el by fxbois 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 | ;;; Code: 23 | 24 | (require 'expand-region-core) 25 | 26 | (defun er/add-web-mode-expansions () 27 | (set (make-local-variable 'er/try-expand-list) 28 | (cons 'web-mode-mark-and-expand er/try-expand-list))) 29 | 30 | (er/enable-mode-expansions 'web-mode #'er/add-web-mode-expansions) 31 | 32 | (provide 'web-mode-expansions) 33 | -------------------------------------------------------------------------------- /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 | #include 8 | 9 | namespace Foo { 10 | struct Bar { 11 | static float val (int x, double y) { return 42.; } 12 | }; 13 | } 14 | 15 | int main (int argc, char **argv) { 16 | int x = 0; 17 | double y = 1.; 18 | float z = Foo::Bar::val (x, y); 19 | char t = argv [x + 3]; 20 | 21 | int i = 0; 22 | for ( ; i 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 | ;; enh-ruby-mode doesn't use ruby-mode's mark-defun - it has its own. 24 | ;; 25 | ;; Feel free to contribute any other expansions for enh-ruby-mode at 26 | ;; 27 | ;; https://github.com/magnars/expand-region.el 28 | 29 | ;;; Code: 30 | 31 | (require 'expand-region-core) 32 | 33 | (defun er/add-enh-ruby-mode-expansions () 34 | "Adds Ruby-specific expansions for buffers in enh-ruby-mode" 35 | (require 'ruby-mode-expansions) 36 | 37 | (set (make-local-variable 'er/try-expand-list) (append 38 | (remove 'er/mark-defun er/try-expand-list) 39 | '(er/mark-ruby-instance-variable 40 | er/mark-ruby-block-up)))) 41 | 42 | (er/enable-mode-expansions 'enh-ruby-mode #'er/add-enh-ruby-mode-expansions) 43 | 44 | (provide 'enh-ruby-mode-expansions) 45 | -------------------------------------------------------------------------------- /erlang-mode-expansions.el: -------------------------------------------------------------------------------- 1 | ;;; erlang-mode-expansions.el --- Erlang-specific expansions for expand-region -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc 4 | 5 | ;; Author: Gleb Peregud 6 | ;; Based on python-mode-expansions by: Ivan Andrus 7 | ;; Keywords: marking region erlang 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 Erlang at 25 | ;; 26 | ;; https://github.com/magnars/expand-region.el 27 | 28 | ;;; Bugs: 29 | 30 | ;; Doesn't handle many Erlang syntax constructs, just the basics 31 | 32 | ;;; Code: 33 | 34 | (require 'expand-region-core) 35 | 36 | (defun er/add-erlang-mode-expansions () 37 | "Adds Erlang-specific expansions for buffers in erlang-mode" 38 | (set (make-local-variable 'er/try-expand-list) (append 39 | er/try-expand-list 40 | '(erlang-mark-function 41 | erlang-mark-clause)))) 42 | 43 | (er/enable-mode-expansions 'erlang-mode #'er/add-erlang-mode-expansions) 44 | 45 | (provide 'erlang-mode-expansions) 46 | 47 | ;; erlang-mode-expansions.el ends here 48 | -------------------------------------------------------------------------------- /features/cperl-mode-expansions.feature: -------------------------------------------------------------------------------- 1 | Feature: cperl-mode expansions 2 | In order to quickly and precisely mark perl variable names 3 | As an Emacs user 4 | I want to expand to them 5 | 6 | Scenario: Mark perl variable name 7 | Given I turn on cperl-mode 8 | And there is no region selected 9 | When I insert: 10 | """ 11 | my $foo = "bar"; 12 | """ 13 | And I place the cursor after "$f" 14 | And I press "C-@" 15 | And I press "C-@" 16 | Then the region should be: 17 | """ 18 | $foo 19 | """ 20 | 21 | Scenario: Mark interpolated perl variable name 22 | Given I turn on cperl-mode 23 | And there is no region selected 24 | When I insert: 25 | """ 26 | my $foo = "something $bar here"; 27 | """ 28 | And I place the cursor after "something " 29 | And I press "C-@" 30 | Then the region should be: 31 | """ 32 | $bar 33 | """ 34 | 35 | Scenario: Mark perl package name 36 | Given I turn on cperl-mode 37 | And there is no region selected 38 | When I insert: 39 | """ 40 | Namespace::Foo::Bar::method_call($baz); 41 | """ 42 | And I place the cursor before "::Foo" 43 | And I press "C-@" 44 | And I press "C-@" 45 | Then the region should be: 46 | """ 47 | Namespace::Foo::Bar 48 | """ 49 | 50 | Scenario: Mark one perl subroutine 51 | Given I turn on cperl-mode 52 | And there is no region selected 53 | When I insert: 54 | """ 55 | sub foo { 56 | foo_do_something; 57 | } 58 | 59 | sub bar { 60 | bar_do_something; 61 | } 62 | 63 | sub baz { 64 | baz_do_something; 65 | } 66 | """ 67 | And I place the cursor before "foo_do_something" 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: 74 | """ 75 | sub foo { 76 | foo_do_something; 77 | } 78 | """ 79 | -------------------------------------------------------------------------------- /subword-mode-expansions.el: -------------------------------------------------------------------------------- 1 | ;;; subword-mode-expansions.el --- Expansions for subword-mode to be used for CamelCase -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2014-2023 Free Software Foundation, Inc 4 | 5 | ;; Author: Lefteris Karapetsas 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 | ;; Provides extra expansions for subword mode so that when 24 | ;; subword-mode is non-nil different words can be selected in CamelCase. 25 | ;; Feel free to contribute any other expansions: 26 | ;; 27 | ;; https://github.com/magnars/expand-region.el 28 | 29 | ;;; Code: 30 | 31 | (require 'expand-region-core) 32 | (require 'subword) 33 | 34 | (defun er/mark-subword () 35 | "Mark a subword, a part of a CamelCase identifier." 36 | (interactive) 37 | (when (and subword-mode 38 | expand-region-subword-enabled) 39 | (subword-right 1) 40 | (set-mark (point)) 41 | (subword-left 1))) 42 | 43 | (defun er/add-subword-mode-expansions () 44 | "Add expansions for buffers in `subword-mode'." 45 | (set (make-local-variable 'er/try-expand-list) 46 | (append er/try-expand-list 47 | '(er/mark-subword)))) 48 | 49 | (er/enable-minor-mode-expansions 'subword-mode 'er/add-subword-mode-expansions) 50 | 51 | (provide 'subword-mode-expansions) 52 | ;;; subword-mode-expansions.el ends here 53 | -------------------------------------------------------------------------------- /css-mode-expansions.el: -------------------------------------------------------------------------------- 1 | ;;; css-mode-expansions.el --- CSS-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 | ;; For now I have only found the need for mark-css-declaration. 24 | ;; 25 | ;; Feel free to contribute any other expansions for CSS at 26 | ;; 27 | ;; https://github.com/magnars/expand-region.el 28 | 29 | ;;; Code: 30 | 31 | (require 'expand-region-core) 32 | 33 | (defun er/mark-css-declaration () 34 | "Marks one CSS declaration, eg. font-weight: bold;" 35 | (interactive) 36 | (search-backward-regexp "[;{] ?" (line-beginning-position)) 37 | (forward-char) 38 | (set-mark (point)) 39 | (search-forward ";" (line-end-position)) 40 | (exchange-point-and-mark)) 41 | 42 | (defun er/add-css-mode-expansions () 43 | "Adds CSS-specific expansions for buffers in css-mode" 44 | (set (make-local-variable 'er/try-expand-list) (append 45 | er/try-expand-list 46 | '(er/mark-css-declaration)))) 47 | 48 | (er/enable-mode-expansions 'css-mode #'er/add-css-mode-expansions) 49 | 50 | (provide 'css-mode-expansions) 51 | 52 | ;; css-mode-expansions.el ends here 53 | -------------------------------------------------------------------------------- /features/er-basic-expansions.feature: -------------------------------------------------------------------------------- 1 | Feature: Basic expansions 2 | 3 | Scenario: Mark URL 4 | Given there is no region selected 5 | And I insert "Here is the link: http://emacsrocks.com :-)" 6 | When I place the cursor after "http" 7 | And I press "C-@" 8 | And I press "C-@" 9 | Then the region should be "http://emacsrocks.com" 10 | 11 | Scenario: Mark email 12 | Given there is no region selected 13 | And I insert "Here is the email: sample@example.com :-)" 14 | When I place the cursor after "sample" 15 | And I press "C-@" 16 | And I press "C-@" 17 | Then the region should be "sample@example.com" 18 | 19 | Scenario: Mark symbol with prefix 20 | Given I turn on emacs-lisp-mode 21 | And I insert "(set 'abc 123)" 22 | When I place the cursor after "abc" 23 | And I press "C-@" 24 | And I press "C-@" 25 | Then the region should be "'abc" 26 | 27 | Scenario: Mark string 28 | Given I turn on emacs-lisp-mode 29 | And I insert "(set 'abc "123")" 30 | When I place the cursor after "2" 31 | And I press "C-@" 32 | And I press "C-@" 33 | Then the region should be ""123"" 34 | 35 | Scenario: Mark word 36 | Given I turn on emacs-lisp-mode 37 | And I insert "(set-default 'abc 123)" 38 | When I place the cursor after "f" 39 | And I press "C-@" 40 | Then the region should be "default" 41 | 42 | Scenario: Mark symbol 43 | Given I turn on emacs-lisp-mode 44 | And I insert "(set-default 'abc 123)" 45 | When I place the cursor after "f" 46 | And I press "C-@" 47 | And I press "C-@" 48 | Then the region should be "set-default" 49 | 50 | Scenario: Mark method call 51 | Given I turn on js-mode 52 | And I insert "document.write('abc');" 53 | When I place the cursor after "write" 54 | And I press "C-@" 55 | And I press "C-@" 56 | Then the region should be "document.write('abc')" 57 | 58 | Scenario: Mark current pair 59 | Given I turn on emacs-lisp-mode 60 | And I insert "((foo)(bar))" 61 | When I place the cursor after "oo)" 62 | And I press "C-@" 63 | Then the region should be "(bar)" 64 | 65 | -------------------------------------------------------------------------------- /features/octave-mode-expansions.feature: -------------------------------------------------------------------------------- 1 | Feature: octave-mod expansions 2 | In order to quickly and precisely mark octave units 3 | As an Emacs user 4 | I want to expand to them 5 | 6 | Scenario: Mark block from inside 7 | Given I turn on octave-mode 8 | And there is no region selected 9 | When I insert: 10 | """ 11 | exprBefore; 12 | for i=1:n, 13 | something; 14 | end; 15 | exprAfter; 16 | """ 17 | And I go to point "26" 18 | And I press "C-@" 19 | And I press "C-@" 20 | Then the region should be: 21 | """ 22 | for i=1:n, 23 | something; 24 | end 25 | """ 26 | 27 | 28 | Scenario: Mark block when looking at it 29 | Given I turn on octave-mode 30 | And there is no region selected 31 | When I insert: 32 | """ 33 | exprBefore; 34 | for i=1:n, 35 | something; 36 | end; 37 | exprAfter; 38 | """ 39 | And I go to point "13" 40 | And I press "C-@" 41 | And I press "C-@" 42 | Then the region should be: 43 | """ 44 | for i=1:n, 45 | something; 46 | end 47 | """ 48 | 49 | 50 | Scenario: Mark block when looking at it inside another block 51 | Given I turn on octave-mode 52 | And there is no region selected 53 | When I insert: 54 | """ 55 | exprBefore; 56 | for i=1:n, 57 | for j=i:k, 58 | something; 59 | end; 60 | end; 61 | exprAfter; 62 | """ 63 | And I go to point "26" 64 | And I press "C-@" 65 | And I press "C-@" 66 | Then the region should be: 67 | """ 68 | for j=i:k, 69 | something; 70 | end 71 | """ 72 | 73 | 74 | Scenario: Mark block from inside while looking at another 75 | Given I turn on octave-mode 76 | And there is no region selected 77 | When I insert: 78 | """ 79 | exprBefore; 80 | for i=1:n, 81 | for j=i:k, 82 | something; 83 | end; 84 | end; 85 | exprAfter; 86 | """ 87 | And I go to point "26" 88 | And I press "C-@" 89 | And I press "C-@" 90 | And I press "C-@" 91 | Then the region should be: 92 | """ 93 | for i=1:n, 94 | for j=i:k, 95 | something; 96 | end; 97 | end 98 | """ 99 | 100 | -------------------------------------------------------------------------------- /sml-mode-expansions.el: -------------------------------------------------------------------------------- 1 | ;;; sml-mode-expansions.el --- Expansions for expand-region to be used in sml-mode -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc 4 | 5 | ;; Author: Alexis Gallagher 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 | ;; Provides extra expansions for sml-mode: 25 | ;; - various expression (case, if, let) 26 | ;; - fun bindings 27 | ;; 28 | ;; Tested with sml-mode version 6.3 29 | ;; 30 | ;; Feel free to contribute any other expansions for SML at 31 | ;; 32 | ;; https://github.com/magnars/expand-region.el 33 | 34 | ;;; Code: 35 | 36 | (require 'expand-region-core) 37 | (declare-function sml-find-matching-starter "sml-mode") 38 | 39 | ;; TODO: comma-delimited elements within a list,tuple,record 40 | ;; TODO: match expression, patterns 41 | ;; TODO: individual field, record type 42 | ;; TODO: head-or-tail, then cons expression 43 | 44 | (defun er/sml-mark-keyword-prefixed-expression () 45 | "Mark the surrounding expression." 46 | (interactive) 47 | (progn 48 | (sml-find-matching-starter '("case" "let" "if" "raise")) 49 | (mark-sexp))) 50 | 51 | 52 | (defun er/add-sml-mode-expansions () 53 | "Adds expansions for buffers in `sml-mode'." 54 | (set (make-local-variable 'er/try-expand-list) 55 | (append er/try-expand-list 56 | '(sml-mark-function 57 | er/sml-mark-keyword-prefixed-expression 58 | mark-sexp)))) 59 | 60 | (er/enable-mode-expansions 'sml-mode #'er/add-sml-mode-expansions) 61 | 62 | (provide 'sml-mode-expansions) 63 | 64 | ;; sml-mode-expansions.el ends here 65 | -------------------------------------------------------------------------------- /features/mark-pairs.feature: -------------------------------------------------------------------------------- 1 | Feature: Mark pairs 2 | In order to quickly and precisely mark pairs 3 | As an Emacs user 4 | I want to expand to them 5 | 6 | Scenario: Mark pair when looking at it 7 | Given there is no region selected 8 | When I insert "... (some parens) ..." 9 | And I go to point "5" 10 | And I press "C-@" 11 | Then the region should be "(some parens)" 12 | 13 | Scenario: Mark pair when looking behind at it 14 | Given there is no region selected 15 | When I insert "... (some parens) ..." 16 | And I go to point "18" 17 | And I press "C-@" 18 | Then the region should be "(some parens)" 19 | 20 | Scenario: Mark inside pairs 21 | Given there is no region selected 22 | When I insert "... (some parens) ..." 23 | And I go to point "10" 24 | And I press "C-@" 25 | And I press "C-@" 26 | Then the region should be "some parens" 27 | 28 | Scenario: Mark child in nested pairs 29 | Given there is no region selected 30 | When I insert "... (some (more parens)) ..." 31 | And I go to point "11" 32 | And I press "C-@" 33 | Then the region should be "(more parens)" 34 | 35 | Scenario: Mark inner parent in nested pairs 36 | Given there is no region selected 37 | When I insert "... (some (more parens)) ..." 38 | And I go to point "11" 39 | And I press "C-@" 40 | And I press "C-@" 41 | Then the region should be "some (more parens)" 42 | 43 | Scenario: Mark outer parent in nested pairs 44 | Given there is no region selected 45 | When I insert "... (some (more parens)) ..." 46 | And I go to point "11" 47 | And I press "C-@" 48 | And I press "C-@" 49 | And I press "C-@" 50 | Then the region should be "(some (more parens))" 51 | 52 | Scenario: Mark outer parent in nested pairs (leftie) 53 | Given there is no region selected 54 | When I insert "... ((some more) parens) ..." 55 | And I go to point "6" 56 | And I press "C-@" 57 | And I press "C-@" 58 | And I press "C-@" 59 | Then the region should be "((some more) parens)" 60 | 61 | Scenario: Mark from behind multiline 62 | Given there is no region selected 63 | When I insert: 64 | """ 65 | (let ((test :test)) 66 | (testing)) 67 | """ 68 | And I place the cursor after ":test))" 69 | And I press "C-@" 70 | Then the region should be "((test :test))" 71 | -------------------------------------------------------------------------------- /js2-mode-expansions.el: -------------------------------------------------------------------------------- 1 | ;;; js2-mode-expansions.el --- Additional expansions for js2-mode -*- 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 specifically for js2-mode, since it has 24 | ;; a semantic parser. 25 | ;; 26 | ;; Feel free to contribute any other expansions for JavaScript at 27 | ;; 28 | ;; https://github.com/magnars/expand-region.el 29 | 30 | ;;; Code: 31 | 32 | (require 'expand-region-core) 33 | (declare-function js2-node-parent-stmt "js2-mode") 34 | (declare-function js2-node-at-point "js2-mode") 35 | (declare-function js2-node-abs-pos "js2-mode") 36 | (declare-function js2-node-len "js2-mode") 37 | 38 | (defun js2-mark-parent-statement () 39 | (interactive) 40 | (let* ((parent-statement (if (not (er/looking-back-exact ";")) 41 | (js2-node-parent-stmt (js2-node-at-point)) 42 | (forward-char -1) 43 | (js2-node-at-point))) 44 | (beg (js2-node-abs-pos parent-statement)) 45 | (end (+ beg (js2-node-len parent-statement)))) 46 | (goto-char beg) 47 | (set-mark end))) 48 | 49 | (defun er/add-js2-mode-expansions () 50 | "Adds expansions for buffers in js2-mode" 51 | (set (make-local-variable 'er/try-expand-list) (append 52 | er/try-expand-list 53 | '(js2-mark-parent-statement)))) 54 | 55 | (er/enable-mode-expansions 'js2-mode #'er/add-js2-mode-expansions) 56 | 57 | (provide 'js2-mode-expansions) 58 | 59 | ;; js2-mode-expansions.el ends here 60 | -------------------------------------------------------------------------------- /jsp-expansions.el: -------------------------------------------------------------------------------- 1 | ;;; jsp-expansions.el --- JSP-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 editing JSP files. To be used in conjunction 24 | ;; with the html-mode expansions 25 | ;; 26 | ;; er/mark-jstl-escape 27 | ;; 28 | ;; These expansions aren't loaded by default, so you'll have to explicitly 29 | ;; ask for them in your init file with: 30 | ;; 31 | ;; (eval-after-load 'sgml-mode '(require 'jsp-expansions)) 32 | ;; 33 | ;; Feel free to contribute any other expansions for JSP at 34 | ;; 35 | ;; https://github.com/magnars/expand-region.el 36 | 37 | ;;; Code: 38 | 39 | (require 'expand-region-core) 40 | 41 | (defun er/mark-jstl-escape () 42 | "Mark jstl-escape presumes that point is outside the brackets. 43 | If point is inside the brackets, they will be marked first anyway." 44 | (interactive) 45 | (when (or (looking-at "\\${") 46 | (er/looking-back-exact "$")) 47 | (forward-char 1) 48 | (search-backward "\$") 49 | (set-mark (point)) 50 | (forward-char 1) 51 | (forward-list) 52 | (exchange-point-and-mark))) 53 | 54 | (defun er/add-jsp-expansions () 55 | "Adds JSP-specific expansions to the buffer" 56 | (set (make-local-variable 'er/try-expand-list) (append 57 | er/try-expand-list 58 | '(er/mark-jstl-escape)))) 59 | 60 | (er/enable-mode-expansions 'html-mode #'er/add-jsp-expansions) 61 | 62 | (provide 'jsp-expansions) 63 | 64 | ;; jsp-expansions.el ends here 65 | -------------------------------------------------------------------------------- /text-mode-expansions.el: -------------------------------------------------------------------------------- 1 | ;;; text-mode-expansions.el --- Expansions for expand-region to be used in text -*- 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 normal text at 25 | ;; 26 | ;; https://github.com/magnars/expand-region.el 27 | 28 | ;;; Code: 29 | 30 | (require 'expand-region-core) 31 | 32 | (defun er/mark-text-sentence () 33 | "Marks one sentence." 34 | (interactive) 35 | ;; The obvious 36 | ;; (backward-sentence 1) (mark-end-of-sentence 1) 37 | ;; doesn't work here because it's repeated and the selection keeps 38 | ;; growing by sentences, which isn't what's wanted. 39 | (forward-sentence 1) 40 | (set-mark (point)) 41 | (backward-sentence 1)) 42 | 43 | (defun er/mark-text-paragraph () 44 | "Marks one paragraph." 45 | (interactive) 46 | (mark-paragraph) 47 | (skip-chars-forward er--space-str)) 48 | 49 | (defun er/add-text-mode-expansions () 50 | "Adds expansions for buffers in `text-mode' except for `html-mode'. 51 | Unfortunately `html-mode' inherits from `text-mode' and 52 | text-mode-expansions don't work well in `html-mode'." 53 | (unless (member major-mode expand-region-exclude-text-mode-expansions) 54 | (set (make-local-variable 'er/try-expand-list) 55 | (append 56 | er/try-expand-list 57 | '(er/mark-text-sentence 58 | er/mark-text-paragraph 59 | mark-page))))) 60 | 61 | (er/enable-mode-expansions 'text-mode #'er/add-text-mode-expansions) 62 | 63 | (provide 'text-mode-expansions) 64 | 65 | ;; text-mode-expansions.el ends here 66 | -------------------------------------------------------------------------------- /cperl-mode-expansions.el: -------------------------------------------------------------------------------- 1 | ;;; cperl-mode-expansions.el --- perl-specific expansions for expand-region -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc 4 | 5 | ;; Author: Kang-min Liu 6 | ;; Keywords: marking region cperl 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 | ;;; Code: 22 | 23 | (require 'expand-region-core) 24 | 25 | (defun er/mark-cperl-variable-name () 26 | "Marks one perl variable" 27 | (interactive) 28 | (forward-word) 29 | (backward-word) 30 | (search-backward-regexp "[@$%]" (line-beginning-position)) 31 | (set-mark (point)) 32 | (forward-char) 33 | (search-forward-regexp "[^a-z_]" (line-end-position)) 34 | (backward-char) 35 | (exchange-point-and-mark)) 36 | 37 | (defun er/mark-cperl-package-name () 38 | "Marks one perl package name" 39 | (interactive) 40 | (forward-sexp) 41 | (backward-sexp) 42 | (set-mark (point)) 43 | (forward-sexp) 44 | (search-backward "::" (line-beginning-position)) 45 | (exchange-point-and-mark)) 46 | 47 | (defun er/mark-cperl-subroutine () 48 | "Marks current subroutine body." 49 | (interactive) 50 | (end-of-defun) 51 | (set-mark (point)) 52 | (beginning-of-defun)) 53 | 54 | (defun er/add-cperl-mode-expansions () 55 | "Add cprel mode expansinos" 56 | (set (make-local-variable 'er/try-expand-list) (append 57 | er/try-expand-list 58 | '(er/mark-cperl-variable-name 59 | er/mark-cperl-package-name 60 | er/mark-cperl-subroutine 61 | )))) 62 | 63 | (er/enable-mode-expansions 'cperl-mode #'er/add-cperl-mode-expansions) 64 | 65 | (provide 'cperl-mode-expansions) 66 | 67 | ;; cperl-mode-expansions.el ends here 68 | -------------------------------------------------------------------------------- /feature-mode-expansions.el: -------------------------------------------------------------------------------- 1 | ;;; feature-mode-expansions.el --- cucumber-specific expansions for expand-region -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc 4 | 5 | ;; Author: Raimon Grau 6 | ;; Based on js-mode-expansions by: Raimon Grau 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 | ;; expanders to mark feature semantic objects like step or scenario 26 | ;; 27 | ;; Expansions: 28 | ;; 29 | ;; 30 | ;; er/mark-feature-scenario 31 | ;; er/mark-feature-step 32 | 33 | (require 'expand-region-core) 34 | 35 | (defun er--block-between-keywords (start-keywords-regexp &optional end-keywords-regexp) 36 | (let* ((start-key-words (concat "^\\( \\)*" start-keywords-regexp)) 37 | (end-key-words (concat "^\\( \\)*" (or end-keywords-regexp start-keywords-regexp)))) 38 | (when (looking-at-p "[^\\s-]") 39 | (skip-syntax-forward "w.")) 40 | (if (looking-at-p start-keywords-regexp) 41 | (progn (beginning-of-line) 42 | (exchange-point-and-mark)) 43 | (re-search-backward start-key-words) 44 | (set-mark (point)) 45 | (re-search-forward start-key-words)) 46 | (unless (re-search-forward end-key-words (point-max) t) 47 | (goto-char (point-max))) 48 | (forward-line 0) 49 | (exchange-point-and-mark))) 50 | 51 | (defun er/mark-feature-scenario () 52 | (interactive) 53 | (er--block-between-keywords "\\(Background:\\|Scenario:\\|Feature:\\)")) 54 | 55 | (defun er/mark-feature-step () 56 | (interactive) 57 | (er--block-between-keywords "\\(And\\|Given\\|When\\|Then\\)" "\\(And\\|Given\\|When\\|Then\\|Scenario:\\)")) 58 | 59 | (defun er/add-feature-mode-expansions () 60 | "Adds cucumber-specific expansions for buffers in feature-mode" 61 | (set (make-local-variable 'er/try-expand-list) (append 62 | er/try-expand-list 63 | '(er/mark-feature-scenario 64 | er/mark-feature-step)))) 65 | 66 | (er/enable-mode-expansions 'feature-mode #'er/add-feature-mode-expansions) 67 | 68 | (provide 'feature-mode-expansions) 69 | -------------------------------------------------------------------------------- /features/step-definitions/expand-region-steps.el: -------------------------------------------------------------------------------- 1 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc -*- lexical-binding: t; -*- 2 | 3 | (Given "^mark is inactive by default$" 4 | (lambda () 5 | (setq set-mark-default-inactive t))) 6 | 7 | (Given "^cursor behaviour is set to smart$" 8 | (lambda () 9 | (setq expand-region-smart-cursor t))) 10 | 11 | (When "^I expand the region$" 12 | (lambda () 13 | (cl-flet ((message (&rest args) nil)) 14 | (er/expand-region 1)))) 15 | 16 | (When "^I quit$" 17 | (lambda () 18 | (cl-flet ((signal (&rest args) nil)) 19 | (keyboard-quit)))) 20 | 21 | (When "^I expand the region \\([0-9]+\\) times$" 22 | (lambda (arg) 23 | (cl-flet ((message (&rest args) nil)) 24 | (er/expand-region (string-to-number arg))))) 25 | 26 | (And "^I contract the region$" 27 | (lambda () 28 | (er/contract-region 1))) 29 | 30 | (When "^I place the cursor after \"\\(.+\\)\"$" 31 | (lambda (arg) 32 | (goto-char (point-min)) 33 | (let ((search (search-forward arg nil t)) 34 | (message "Can not place cursor after '%s', because there is no such point: '%s'")) 35 | (cl-assert search nil message arg (espuds-buffer-contents))))) 36 | 37 | (When "^I place the cursor before \"\\(.+\\)\"$" 38 | (lambda (arg) 39 | (goto-char (point-max)) 40 | (let ((search (search-backward arg nil t)) 41 | (message "Can not place cursor before '%s', because there is no such point: '%s'")) 42 | (cl-assert search nil message arg (espuds-buffer-contents))))) 43 | 44 | (When "^I pop the mark$" 45 | (lambda () 46 | (set-mark-command 4))) 47 | 48 | (When "^I deactivate the mark$" 49 | (lambda () 50 | (deactivate-mark))) 51 | 52 | (When "^I activate the mark$" 53 | (lambda () 54 | (activate-mark))) 55 | 56 | (Then "^the region should not be active$" 57 | (lambda () 58 | (should 59 | (not (region-active-p))))) 60 | 61 | (Then "^cursor should be at point \"\\(.+\\)\"$" 62 | (lambda (arg) 63 | (should 64 | (= 65 | (string-to-number arg) 66 | (point))))) 67 | 68 | (And "^autocopy-register is \"\\(.\\)\"$" 69 | (lambda (reg) 70 | (setq expand-region-autocopy-register reg) 71 | (set-register (aref reg 0) nil))) 72 | 73 | (Then "^register \"\\(.\\)\" should be \"\\(.+\\)\"$" 74 | (lambda (reg contents) 75 | (should 76 | (equal contents (get-register (aref reg 0)))))) 77 | 78 | (When "^I go to the \\(front\\|end\\) of the word \"\\(.+\\)\"$" 79 | (lambda (pos word) 80 | (goto-char (point-min)) 81 | (let ((search (re-search-forward (format "%s" word) nil t)) 82 | (message "Can not go to character '%s' since it does not exist in the current buffer: %s")) 83 | (cl-assert search nil message word (espuds-buffer-contents)) 84 | (if (string-equal "front" pos) (backward-word))))) 85 | 86 | (When "^I set \\(.+\\) to \\(.+\\)$" 87 | (lambda (var val) 88 | (set (intern var) (read val)))) 89 | ;; Local Variables: 90 | ;; no-byte-compile: t 91 | ;; End: 92 | -------------------------------------------------------------------------------- /octave-expansions.el: -------------------------------------------------------------------------------- 1 | ;;; octave-expansions.el --- octave-mode expansions for expand-region -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc 4 | 5 | ;; Author: Mark Hepburn 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 | ;; Feel free to contribute any other expansions for Octave at 24 | ;; 25 | ;; https://github.com/magnars/expand-region.el 26 | 27 | ;;; Code: 28 | 29 | (require 'expand-region-core) 30 | (declare-function octave-mark-block "octave-mod") 31 | 32 | ;;; Octave-mod received a major rewrite between versions 23 and 24 of 33 | ;;; Emacs, for example using the new smie package instead of 34 | ;;; hand-coding a lot of motion commands. Unfortunately for our 35 | ;;; purposes here, in the process the behaviour of `octave-mark-block' 36 | ;;; changed slightly. So, in order to behave identically across both 37 | ;;; versions we need to check which is which in a few places and 38 | ;;; adjust accordingly: 39 | (defconst er/old-octave-mod-p (fboundp 'octave-up-block)) 40 | 41 | (defalias 'er/up-block 42 | (if er/old-octave-mod-p 'octave-up-block 'up-list)) 43 | 44 | (defun er/octave-mark-up-block () 45 | "Mark the containing block, assuming the current block has 46 | already been marked." 47 | (interactive) 48 | (when (use-region-p) 49 | (when (< (point) (mark)) 50 | (exchange-point-and-mark)) 51 | (er/up-block -1) ; -1 means backwards, ie to the front 52 | (octave-mark-block))) 53 | 54 | (defun er/octave-mark-block () 55 | "Not for general use; this is a work-around for the different 56 | behaviour of `octave-mark-block' between emacs versions 23 and 57 | 24." 58 | (interactive) 59 | (forward-word) 60 | (octave-mark-block)) 61 | 62 | (defun er/add-octave-expansions () 63 | "Adds octave/matlab-specific expansions for buffers in octave-mode" 64 | (let ((try-expand-list-additions (if er/old-octave-mod-p 65 | '(octave-mark-block 66 | er/octave-mark-up-block 67 | octave-mark-defun) 68 | '(octave-mark-block 69 | er/octave-mark-block 70 | er/octave-mark-up-block 71 | mark-defun)))) 72 | (set (make-local-variable 'er/try-expand-list) 73 | (append er/try-expand-list try-expand-list-additions)))) 74 | 75 | (er/enable-mode-expansions 'octave-mode #'er/add-octave-expansions) 76 | 77 | (provide 'octave-expansions) 78 | ;;; octave-expansions.el ends here 79 | -------------------------------------------------------------------------------- /features/text-mode-expansions.feature: -------------------------------------------------------------------------------- 1 | Feature: Text-mode expansions 2 | Background: 3 | Given there is no region selected 4 | And I turn on text-mode 5 | And I insert: 6 | """ 7 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 8 | Here is a sentence. Here is another. And one with Dr. Baker. 9 | 10 | Another paragraph. With 2 sentences. 11 | 12 | "We're on a different page," said the man. 13 | """ 14 | 15 | Scenario: Mark sentence ending on a line 16 | When I place the cursor after "consectetur" 17 | And I press "C-@" 18 | Then the region should be "consectetur" 19 | And I press "C-@" 20 | Then the region should be "Lorem ipsum dolor sit amet, consectetur adipiscing elit." 21 | And I press "C-@" 22 | Then the region should be: 23 | """ 24 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 25 | Here is a sentence. Here is another. And one with Dr. Baker. 26 | 27 | """ 28 | 29 | Scenario: Mark sentence ending on a line 2 30 | When I place the cursor before "Lorem" 31 | And I press "C-@" 32 | Then the region should be "Lorem" 33 | And I press "C-@" 34 | Then the region should be "Lorem ipsum dolor sit amet, consectetur adipiscing elit." 35 | And I press "C-@" 36 | Then the region should be: 37 | """ 38 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 39 | Here is a sentence. Here is another. And one with Dr. Baker. 40 | 41 | """ 42 | 43 | Scenario: Mark sentence beginning a line 44 | When I place the cursor after "sentence." 45 | And I press "C-@" 46 | Then the region should be "sentence." 47 | And I press "C-@" 48 | Then the region should be "Here is a sentence." 49 | And I press "C-@" 50 | Then the region should be: 51 | """ 52 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 53 | Here is a sentence. Here is another. And one with Dr. Baker. 54 | 55 | """ 56 | 57 | Scenario: Mark sentence in the middle of a line 58 | When I place the cursor before "is another" 59 | And I press "C-@" 60 | Then the region should be "is" 61 | And I press "C-@" 62 | Then the region should be "Here is another." 63 | And I press "C-@" 64 | Then the region should be: 65 | """ 66 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 67 | Here is a sentence. Here is another. And one with Dr. Baker. 68 | 69 | """ 70 | 71 | Scenario: Mark sentence in the middle of a line 72 | When I place the cursor after "Baker." 73 | And I press "C-@" 74 | Then the region should be "Baker." 75 | And I press "C-@" 76 | Then the region should be "And one with Dr. Baker." 77 | And I press "C-@" 78 | Then the region should be: 79 | """ 80 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 81 | Here is a sentence. Here is another. And one with Dr. Baker. 82 | 83 | """ 84 | 85 | Scenario: Sentence endings 86 | When I place the cursor before "Dr." 87 | And I set sentence-end-double-space to nil 88 | And I press "C-u 3 C-@" 89 | Then the region should be "And one with Dr." 90 | 91 | Scenario: Sentence endings 2 92 | When I place the cursor before "Dr." 93 | And I set sentence-end-double-space to t 94 | And I press "C-u 3 C-@" 95 | Then the region should be "And one with Dr. Baker." 96 | # I turned sentence-end-double-space back to the default here in 97 | # case it comes into play in other tests. 98 | -------------------------------------------------------------------------------- /python-el-expansions.el: -------------------------------------------------------------------------------- 1 | ;;; python-el-expansions.el --- Python-specific expansions for expand-region -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2012-2023 Free Software Foundation, Inc 4 | 5 | ;; Authors: Ivan Andrus, Felix Geller, @edmccard 6 | ;; Based on js-mode-expansions by: Magnar Sveen 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 | ;; For python.el included with GNU Emacs 25 | ;; - Mark functionality taken from python.el: 26 | ;; - `python-mark-block' 27 | ;; - Additions implemented here: 28 | ;; - `er/mark-python-statement' 29 | ;; - `er/mark-inside-python-string' 30 | ;; - `er/mark-outside-python-string' 31 | ;; - Supports multi-line strings 32 | 33 | ;; There is no need for a er/mark-python-defun since 34 | ;; er/mark-python-block will mark it 35 | 36 | ;; Feel free to contribute any other expansions for Python at 37 | ;; 38 | ;; https://github.com/magnars/expand-region.el 39 | 40 | ;;; Code: 41 | 42 | (require 'expand-region-core) 43 | (require 'python) 44 | 45 | (declare-function python-beginning-of-string "python-mode") 46 | 47 | (defvar er--python-string-delimiter "'\"") 48 | 49 | (defun er/mark-python-statement () 50 | "Marks one Python statement, eg. x = 3" 51 | (interactive) 52 | (python-nav-end-of-statement) 53 | (set-mark (point)) 54 | (python-nav-beginning-of-statement)) 55 | 56 | (defun er/mark-outside-python-string () 57 | "Marks region outside a (possibly multi-line) Python string" 58 | (interactive) 59 | (python-beginning-of-string) 60 | (set-mark (point)) 61 | (forward-sexp) 62 | (exchange-point-and-mark)) 63 | 64 | (defun er/mark-inside-python-string () 65 | "Marks region inside a (possibly multi-line) Python string" 66 | (interactive) 67 | (when (eq 'string (syntax-ppss-context (syntax-ppss))) 68 | (python-beginning-of-string) 69 | (let ((string-beginning (point))) 70 | (forward-sexp) 71 | (skip-chars-backward er--python-string-delimiter) 72 | (set-mark (point)) 73 | (goto-char string-beginning) 74 | (skip-chars-forward er--python-string-delimiter)))) 75 | 76 | (defun er/add-python-mode-expansions () 77 | "Adds Python-specific expansions for buffers in python-mode" 78 | (let ((try-expand-list-additions '(er/mark-python-statement 79 | er/mark-inside-python-string 80 | er/mark-outside-python-string 81 | python-mark-block))) 82 | (set (make-local-variable 'expand-region-skip-whitespace) nil) 83 | (set (make-local-variable 'er/try-expand-list) 84 | (remove 'er/mark-inside-quotes 85 | (remove 'er/mark-outside-quotes 86 | (append er/try-expand-list try-expand-list-additions)))))) 87 | 88 | (er/enable-mode-expansions 'python-mode #'er/add-python-mode-expansions) 89 | 90 | (provide 'python-el-expansions) 91 | 92 | ;; python-el-expansions.el ends here 93 | -------------------------------------------------------------------------------- /features/html-mode-expansions.feature: -------------------------------------------------------------------------------- 1 | Feature: html-mode expansions 2 | In order to quickly and precisely mark html units 3 | As an Emacs user 4 | I want to expand to them 5 | 6 | Scenario: Mark html attribute from start 7 | Given I turn on html-mode 8 | And there is no region selected 9 | When I insert "
" 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 "...
before
after
..." 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 "...
before
after
..." 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 "...
before
after
..." 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 "...
before
after
..." 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 "...
before
after
..." 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 "...
before
after
..." 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 "
before
after
" 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 " 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 "...
before
after
..." 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 "...
before
after
..." 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 "...
before
after
..." 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 "...
before
after
..." 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 "...
before
after
..." 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 "...
before
after
..." 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 "
before
after
" 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 | [![Build Status](https://secure.travis-ci.org/magnars/expand-region.el.png)](http://travis-ci.org/magnars/expand-region.el) 2 | [![Coverage Status](https://coveralls.io/repos/magnars/expand-region.el/badge.svg?branch=master&service=github)](https://coveralls.io/github/magnars/expand-region.el) 3 | [![GNU ELPA](https://elpa.gnu.org/packages/expand-region.svg)](https://elpa.gnu.org/packages/expand-region.html) 4 | [![MELPA](https://melpa.org/packages/expand-region-badge.svg)](https://melpa.org/#/expand-region) 5 | [![MELPA Stable](https://stable.melpa.org/packages/expand-region-badge.svg)](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 | --------------------------------------------------------------------------------