-
Notifications
You must be signed in to change notification settings - Fork 133
/
Copy pathpython-el-fgallina-expansions.el
193 lines (165 loc) · 7.2 KB
/
python-el-fgallina-expansions.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
;;; python-el-fgallina-expansions.el --- fgallina/python.el-specific expansions for expand-region -*- lexical-binding: t; -*-
;; Copyright (C) 2012-2023 Free Software Foundation, Inc
;; Author: Felix Geller
;; Keywords: marking region python
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; - Additions implemented here:
;; - `er/mark-inside-python-string'
;; - `er/mark-outside-python-string'
;; - `er/mark-python-statement'
;; - `er/mark-python-block'
;; - `er/mark-outer-python-block'
;; - `er/mark-python-block-and-decorator'
;; - Supports multi-line strings
;;; Code:
(require 'expand-region-core)
(if (not (fboundp 'python-syntax-context))
(defalias 'python-syntax-context #'python-info-ppss-context))
(if (not (fboundp 'python-indent-offset))
(defalias 'python-indent-offset #'python-indent))
(defvar er--python-string-delimiter
"'\""
"Characters that delimit a Python string.")
;; copied from @fgallina's python.el as a quick fix. The variable
;; `python-rx-constituents' is not bound when we use the python-rx
;; macro from here, so we have to construct the regular expression
;; manually.
(defvar er--python-block-start-regex
(rx symbol-start
(or "def" "class" "if" "elif" "else" "try"
"except" "finally" "for" "while" "with")
symbol-end)
"Regular expression string to match the beginning of a Python block.")
(defun er/mark-python-string (mark-inside)
"Mark the Python string that surrounds point.
If the optional MARK-INSIDE is not nil, only mark the region
between the string delimiters, otherwise the region includes the
delimiters as well."
(let ((beginning-of-string (python-syntax-context 'string (syntax-ppss))))
(when beginning-of-string
(goto-char beginning-of-string)
;; Move inside the string, so we can use ppss to find the end of
;; the string.
(skip-chars-forward er--python-string-delimiter)
(while (python-syntax-context 'string (syntax-ppss))
(forward-char 1))
(when mark-inside (skip-chars-backward er--python-string-delimiter))
(set-mark (point))
(goto-char beginning-of-string)
(when mark-inside (skip-chars-forward er--python-string-delimiter)))))
(defun er/mark-inside-python-string ()
"Mark the inside of the Python string that surrounds point.
Command that wraps `er/mark-python-string'."
(interactive)
(er/mark-python-string t))
(defun er/mark-outside-python-string ()
"Mark the outside of the Python string that surrounds point.
Command that wraps `er/mark-python-string'."
(interactive)
(er/mark-python-string nil))
(defun er/mark-python-statement ()
"Mark the Python statement that surrounds point."
(interactive)
(python-nav-end-of-statement)
(set-mark (point))
(python-nav-beginning-of-statement))
(defun er/mark-python-block (&optional next-indent-level)
"Mark the Python block that surrounds point.
If the optional NEXT-INDENT-LEVEL is given, select the
surrounding block that is defined at an indentation that is less
than NEXT-INDENT-LEVEL."
(interactive)
(back-to-indentation)
(let ((next-indent-level
(or
;; Use the given level
next-indent-level
;; Check whether point is at the start of a Python block.
(if (looking-at er--python-block-start-regex)
;; Block start means that the next level is deeper.
(+ (current-indentation) python-indent-offset)
;; Assuming we're inside the block that we want to mark
(current-indentation)))))
;; Move point to next Python block start at the correct indent-level
(while (>= (current-indentation) next-indent-level)
(re-search-backward er--python-block-start-regex))
;; Mark the beginning of the block
(set-mark (point))
;; Save indentation and look for the end of this block
(let ((block-indentation (current-indentation)))
(forward-line 1)
(while (and
;; No need to go beyond the end of the buffer. Can't use
;; eobp as the loop places the point at the beginning of
;; line, but eob might be at the end of the line.
(not (= (point-max) (line-end-position)))
;; Proceed if: indentation is too deep
(or (> (current-indentation) block-indentation)
;; Looking at an empty line
(looking-at (rx line-start (* whitespace) line-end))
;; We're not looking at the start of a Python block
;; and the indent is deeper than the block's indent
(and (not (looking-at er--python-block-start-regex))
(> (current-indentation) block-indentation))))
(forward-line 1)
(back-to-indentation))
;; Find the end of the block by skipping comments backwards
(python-util-forward-comment -1)
(exchange-point-and-mark))))
(defun er/mark-outer-python-block ()
"Mark the Python block that surrounds the Python block around point.
Command that wraps `er/mark-python-block'."
(interactive)
(er/mark-python-block (current-indentation)))
(defun er/mark-python-block-and-decorator ()
(interactive)
(back-to-indentation)
(if (or (er--python-looking-at-decorator) (er--python-looking-at-decorator -1))
(progn
(while (er--python-looking-at-decorator -1)
(forward-line -1)
(back-to-indentation)
)
(set-mark (point))
(while (er--python-looking-at-decorator)
(forward-line)
)
(python-nav-end-of-block)
(exchange-point-and-mark))))
(defun er--python-looking-at-decorator (&optional line-offset)
(save-excursion
(if line-offset
(forward-line line-offset)
)
(back-to-indentation)
(looking-at "@")
))
(defun er/add-python-mode-expansions ()
"Adds python-mode-specific expansions for buffers in python-mode"
(let ((try-expand-list-additions '(
er/mark-inside-python-string
er/mark-outside-python-string
er/mark-python-statement
er/mark-python-block
er/mark-python-block-and-decorator
er/mark-outer-python-block
)))
(set (make-local-variable 'expand-region-skip-whitespace) nil)
(set (make-local-variable 'er/try-expand-list)
(remove 'er/mark-inside-quotes
(remove 'er/mark-outside-quotes
(append er/try-expand-list try-expand-list-additions))))))
(er/enable-mode-expansions 'python-mode #'er/add-python-mode-expansions)
(provide 'python-el-fgallina-expansions)
;; python-el-fgallina-expansions.el ends here