dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

python-el-fgallina-expansions.el (7371B)


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