Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
194 lines (165 sloc) 7.14 KB
;;; python-el-fgallina-expansions.el --- fgallina/python.el-specific expansions for expand-region
;; Copyright (C) 2012 Felix Geller
;; 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
;; 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 <>.
;;; 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")
"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'."
(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'."
(er/mark-python-string nil))
(defun er/mark-python-statement ()
"Mark the Python statement that surrounds point."
(set-mark (point))
(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
(let ((next-indent-level
;; Use the given 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
;; 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) (point-at-eol)))
;; 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)
;; Find the end of the block by skipping comments backwards
(python-util-forward-comment -1)
(defun er/mark-outer-python-block ()
"Mark the Python block that surrounds the Python block around point.
Command that wraps `er/mark-python-block'."
(er/mark-python-block (current-indentation)))
(defun er/mark-python-block-and-decorator ()
(if (or (er--python-looking-at-decorator) (er--python-looking-at-decorator -1))
(while (er--python-looking-at-decorator -1)
(forward-line -1)
(set-mark (point))
(while (er--python-looking-at-decorator)
(defun er--python-looking-at-decorator (&optional line-offset)
(if line-offset
(forward-line line-offset)
(looking-at "@")
(defun er/add-python-mode-expansions ()
"Adds python-mode-specific expansions for buffers in python-mode"
(let ((try-expand-list-additions '(
(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