Skip to content

Commit

Permalink
Make Elpy aware of decorators (#1705)
Browse files Browse the repository at this point in the history
* Make Elpy aware of decorators

* Added tests

* Update the documentation
  • Loading branch information
galaunay committed Oct 22, 2019
1 parent 4b79ed2 commit 7fb3b2e
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 4 deletions.
5 changes: 3 additions & 2 deletions docs/ide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,9 @@ for each combination of: whether or not the point should move after sending

Send the current statement to the Python shell and keep point position. Here
statement refers to the Python statement the point is on, including
potentially nested statements and, if point is on an if/elif/else clause, the
entire if statement (with all its elif/else clauses).
potentially nested statements. If point is on an if/elif/else clause send the
entire if statement (with all its elif/else clauses). If point is on a
decorated function, send the decorator as well.

.. command:: elpy-shell-send-statement-and-step
:kbd: C-c C-y C-e
Expand Down
20 changes: 18 additions & 2 deletions elpy-shell.el
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,13 @@ BEGIN and END refer to the region of the current buffer containing the code bein
(defun elpy-shell--current-line-else-or-elif-p ()
(eq (string-match-p "\\s-*el\\(?:se:\\|if[^\w]\\)" (thing-at-point 'line)) 0))

(defun elpy-shell--current-line-decorator-p ()
(eq (string-match-p "^\\s-*@[A-Za-z]" (thing-at-point 'line)) 0))

(defun elpy-shell--current-line-decorated-defun-p ()
(save-excursion (python-nav-backward-statement)
(elpy-shell--current-line-decorator-p)))

(defun elpy-shell--current-line-indented-p ()
(eq (string-match-p "\\s-+[^\\s-]+" (thing-at-point 'line)) 0))

Expand Down Expand Up @@ -691,7 +698,8 @@ there."
(python-nav-beginning-of-statement)
(let ((p))
(while (and (not (eq p (point)))
(elpy-shell--current-line-else-or-elif-p))
(or (elpy-shell--current-line-else-or-elif-p)
(elpy-shell--current-line-decorated-defun-p)))
(elpy-nav-backward-block)
(setq p (point)))))

Expand All @@ -705,6 +713,10 @@ statement (e.g., after calling
(p))
(while (and (not (eq p (point)))
continue)
;; if on a decorator, move to the associated function
(when (elpy-shell--current-line-decorator-p)
(elpy-nav-forward-block))

;; check if there is a another block at the same indentation level
(setq p (point))
(elpy-nav-forward-block)
Expand Down Expand Up @@ -786,7 +798,11 @@ definition and returns t. Otherwise, retains point position and
returns nil.
See `elpy-shell--nav-beginning-of-def' for details."
(elpy-shell--nav-beginning-of-def 'elpy-shell--current-line-defun-p))
(when (or (elpy-shell--nav-beginning-of-def 'elpy-shell--current-line-defun-p)
(elpy-shell--current-line-decorator-p))
(when (elpy-shell--current-line-decorated-defun-p)
(python-nav-backward-statement))
t))

(defun elpy-shell--nav-beginning-of-defclass ()
"Move point to the beginning of the current class definition.
Expand Down
120 changes: 120 additions & 0 deletions test/elpy-shell-send-defun-test.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
(ert-deftest elpy-shell-send-defun-should-send-defun-at-point ()
(elpy-testcase ()
(python-mode)
(elpy-mode)
(setq elpy-shell-echo-input nil)
(setq elpy-shell-echo-output nil)
(insert "def foo():
1+1
for i in range(10):
a = 2+2
4+3
b = a+i
print(b)
")

;; on "a = 2+2"
(goto-char 52)
(elpy-shell-kill t)
(elpy-shell-send-defun)
(python-shell-send-string "foo()\n")
(python-shell-send-string "print('OK')\n")
(should (string-match ">>> 13"
(with-current-buffer "*Python*"
(elpy/wait-for-output "OK" 30)
(buffer-string))))
;; on "for i in range(10):"
(goto-char 30)
(elpy-shell-kill t)
(elpy-shell-send-defun)
(python-shell-send-string "foo()\n")
(python-shell-send-string "print('OK')\n")
(should (string-match ">>> 13"
(with-current-buffer "*Python*"
(elpy/wait-for-output "OK" 30)
(buffer-string))))
;; on "def foo():"
(goto-char 6)
(elpy-shell-kill t)
(elpy-shell-send-defun)
(python-shell-send-string "foo()\n")
(python-shell-send-string "print('OK')\n")
(should (string-match ">>> 13"
(with-current-buffer "*Python*"
(elpy/wait-for-output "OK" 30)
(buffer-string))))))

(ert-deftest elpy-shell-send-defun-should-send-defun-and-decorator ()
(elpy-testcase ()
(python-mode)
(elpy-mode)
(setq elpy-shell-echo-input nil)
(setq elpy-shell-echo-output nil)
(insert "def deco(f):
def f2():
print('in decorator')
f()
return f2
@deco
def foo():
1+1
for i in range(10):
a = 2+2
4+3
b = a+i
print(b)
")

;; on "a = 2+2"
(elpy-shell-kill t)
;; send deco definition
(goto-char 4)
(elpy-shell-send-defun)
(goto-char 145)
(elpy-shell-send-defun)
(python-shell-send-string "foo()\n")
(python-shell-send-string "print('OK')\n")
(should (string-match "in decorator"
(with-current-buffer "*Python*"
(elpy/wait-for-output "OK" 30)
(buffer-string))))
;; on "for i in range(10):"
(elpy-shell-kill t)
;; send deco definition
(goto-char 4)
(elpy-shell-send-defun)
(goto-char 119)
(elpy-shell-send-defun)
(python-shell-send-string "foo()\n")
(python-shell-send-string "print('OK')\n")
(should (string-match "in decorator"
(with-current-buffer "*Python*"
(elpy/wait-for-output "OK" 30)
(buffer-string))))
;; on "def foo():"
(elpy-shell-kill t)
;; send deco definition
(goto-char 4)
(elpy-shell-send-defun)
(goto-char 96)
(elpy-shell-send-defun)
(python-shell-send-string "foo()\n")
(python-shell-send-string "print('OK')\n")
(should (string-match "in decorator"
(with-current-buffer "*Python*"
(elpy/wait-for-output "OK" 30)
(buffer-string))))
;; on "@deco"
(elpy-shell-kill t)
;; send deco definition
(goto-char 4)
(elpy-shell-send-statement)
(goto-char 87)
(elpy-shell-send-statement)
(python-shell-send-string "foo()\n")
(python-shell-send-string "print('OK')\n")
(should (string-match "in decorator"
(with-current-buffer "*Python*"
(elpy/wait-for-output "OK" 30)
(buffer-string))))))
49 changes: 49 additions & 0 deletions test/elpy-shell-send-statement-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,52 @@
(elpy/wait-for-output "OK" 30)
(buffer-string))))
))

(ert-deftest elpy-shell-send-statement-should-send-statement-and-decorator ()
(elpy-testcase ()
(python-mode)
(elpy-mode)
(setq elpy-shell-echo-input nil)
(setq elpy-shell-echo-output nil)
(insert "def deco(f):
def f2():
print('in decorator')
f()
return f2
@deco
def foo():
1+1
for i in range(10):
a = 2+2
4+3
b = a+i
print(b)
")

;; on "foo"
(elpy-shell-kill t)
;; send deco definition
(goto-char 4)
(elpy-shell-send-statement)
(goto-char 96)
(elpy-shell-send-statement)
(python-shell-send-string "foo()\n")
(python-shell-send-string "print('OK')\n")
(should (string-match "in decorator"
(with-current-buffer "*Python*"
(elpy/wait-for-output "OK" 30)
(buffer-string))))
;; on "@deco"
(elpy-shell-kill t)
;; send deco definition
(goto-char 4)
(elpy-shell-send-statement)
(goto-char 86)
(elpy-shell-send-statement)
(python-shell-send-string "foo()\n")
(python-shell-send-string "print('OK')\n")
(should (string-match "in decorator"
(with-current-buffer "*Python*"
(elpy/wait-for-output "OK" 30)
(buffer-string))))))

0 comments on commit 7fb3b2e

Please sign in to comment.