/ python-mode-expansions.el
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 <http://www.gnu.org/licenses/>.
 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