aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lisp/progmodes/python.el57
-rw-r--r--test/lisp/progmodes/python-tests.el197
2 files changed, 247 insertions, 7 deletions
diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el
index e1347754c4a..d3ffc2db2c9 100644
--- a/lisp/progmodes/python.el
+++ b/lisp/progmodes/python.el
@@ -1524,6 +1524,10 @@ marks the next defun after the ones already marked."
1524The name of the defun should be grouped so it can be retrieved 1524The name of the defun should be grouped so it can be retrieved
1525via `match-string'.") 1525via `match-string'.")
1526 1526
1527(defvar python-nav-beginning-of-block-regexp
1528 (python-rx line-start (* space) block-start)
1529 "Regexp matching block start.")
1530
1527(defun python-nav--beginning-of-defun (&optional arg) 1531(defun python-nav--beginning-of-defun (&optional arg)
1528 "Internal implementation of `python-nav-beginning-of-defun'. 1532 "Internal implementation of `python-nav-beginning-of-defun'.
1529With positive ARG search backwards, else search forwards." 1533With positive ARG search backwards, else search forwards."
@@ -4916,9 +4920,37 @@ Interactively, prompt for symbol."
4916(defun python-hideshow-forward-sexp-function (_arg) 4920(defun python-hideshow-forward-sexp-function (_arg)
4917 "Python specific `forward-sexp' function for `hs-minor-mode'. 4921 "Python specific `forward-sexp' function for `hs-minor-mode'.
4918Argument ARG is ignored." 4922Argument ARG is ignored."
4919 (python-nav-end-of-defun) 4923 (python-nav-end-of-block))
4920 (unless (python-info-current-line-empty-p) 4924
4921 (backward-char))) 4925(defun python-hideshow-find-next-block (regexp maxp comments)
4926 "Python specific `hs-find-next-block' function for `hs-minor-mode'.
4927Call `python-nav-forward-block' to find next block and check if
4928block-start ends within MAXP. If COMMENTS is not nil, comments
4929are also searched. REGEXP is passed to `looking-at' to set
4930`match-data'."
4931 (let* ((next-block (save-excursion
4932 (or (and
4933 (python-info-looking-at-beginning-of-block)
4934 (re-search-forward
4935 (python-rx block-start) maxp t))
4936 (and (python-nav-forward-block)
4937 (< (point) maxp)
4938 (re-search-forward
4939 (python-rx block-start) maxp t))
4940 (1+ maxp))))
4941 (next-comment
4942 (or (when comments
4943 (save-excursion
4944 (cl-loop while (re-search-forward "#" maxp t)
4945 if (python-syntax-context 'comment)
4946 return (point))))
4947 (1+ maxp)))
4948 (next-block-or-comment (min next-block next-comment)))
4949 (when (<= next-block-or-comment maxp)
4950 (goto-char next-block-or-comment)
4951 (save-excursion
4952 (beginning-of-line)
4953 (looking-at regexp)))))
4922 4954
4923 4955
4924;;; Imenu 4956;;; Imenu
@@ -5415,6 +5447,16 @@ instead of the current physical line."
5415 (beginning-of-line 1) 5447 (beginning-of-line 1)
5416 (looking-at python-nav-beginning-of-defun-regexp)))) 5448 (looking-at python-nav-beginning-of-defun-regexp))))
5417 5449
5450(defun python-info-looking-at-beginning-of-block ()
5451 "Check if point is at the beginning of block."
5452 (let ((pos (point)))
5453 (save-excursion
5454 (python-nav-beginning-of-statement)
5455 (beginning-of-line)
5456 (and
5457 (<= (point) pos (+ (point) (current-indentation)))
5458 (looking-at python-nav-beginning-of-block-regexp)))))
5459
5418(defun python-info-current-line-comment-p () 5460(defun python-info-current-line-comment-p ()
5419 "Return non-nil if current line is a comment line." 5461 "Return non-nil if current line is a comment line."
5420 (char-equal 5462 (char-equal
@@ -5870,14 +5912,17 @@ REPORT-FN is Flymake's callback function."
5870 5912
5871 (add-to-list 5913 (add-to-list
5872 'hs-special-modes-alist 5914 'hs-special-modes-alist
5873 '(python-mode 5915 `(python-mode
5874 "\\s-*\\_<\\(?:def\\|class\\)\\_>" 5916 ,python-nav-beginning-of-block-regexp
5875 ;; Use the empty string as end regexp so it doesn't default to 5917 ;; Use the empty string as end regexp so it doesn't default to
5876 ;; "\\s)". This way parens at end of defun are properly hidden. 5918 ;; "\\s)". This way parens at end of defun are properly hidden.
5877 "" 5919 ""
5878 "#" 5920 "#"
5879 python-hideshow-forward-sexp-function 5921 python-hideshow-forward-sexp-function
5880 nil)) 5922 nil
5923 python-nav-beginning-of-block
5924 python-hideshow-find-next-block
5925 python-info-looking-at-beginning-of-block))
5881 5926
5882 (setq-local outline-regexp (python-rx (* space) block-start)) 5927 (setq-local outline-regexp (python-rx (* space) block-start))
5883 (setq-local outline-level 5928 (setq-local outline-level
diff --git a/test/lisp/progmodes/python-tests.el b/test/lisp/progmodes/python-tests.el
index 12ac871fdf8..906f7eca7de 100644
--- a/test/lisp/progmodes/python-tests.el
+++ b/test/lisp/progmodes/python-tests.el
@@ -5730,6 +5730,39 @@ def \\
5730 (should (not (python-info-looking-at-beginning-of-defun))) 5730 (should (not (python-info-looking-at-beginning-of-defun)))
5731 (should (not (python-info-looking-at-beginning-of-defun nil t))))) 5731 (should (not (python-info-looking-at-beginning-of-defun nil t)))))
5732 5732
5733(ert-deftest python-info-looking-at-beginning-of-block-1 ()
5734 (python-tests-with-temp-buffer
5735 "
5736def f():
5737 if True:
5738 pass
5739 l = [x * 2
5740 for x in range(5)
5741 if x < 3]
5742# if False:
5743\"\"\"
5744if 0:
5745\"\"\"
5746"
5747 (python-tests-look-at "def f():")
5748 (should (python-info-looking-at-beginning-of-block))
5749 (forward-char)
5750 (should (not (python-info-looking-at-beginning-of-block)))
5751 (python-tests-look-at "if True:")
5752 (should (python-info-looking-at-beginning-of-block))
5753 (forward-char)
5754 (should (not (python-info-looking-at-beginning-of-block)))
5755 (beginning-of-line)
5756 (should (python-info-looking-at-beginning-of-block))
5757 (python-tests-look-at "for x")
5758 (should (not (python-info-looking-at-beginning-of-block)))
5759 (python-tests-look-at "if x < 3")
5760 (should (not (python-info-looking-at-beginning-of-block)))
5761 (python-tests-look-at "if False:")
5762 (should (not (python-info-looking-at-beginning-of-block)))
5763 (python-tests-look-at "if 0:")
5764 (should (not (python-info-looking-at-beginning-of-block)))))
5765
5733(ert-deftest python-info-current-line-comment-p-1 () 5766(ert-deftest python-info-current-line-comment-p-1 ()
5734 (python-tests-with-temp-buffer 5767 (python-tests-with-temp-buffer
5735 " 5768 "
@@ -6183,8 +6216,11 @@ class SomeClass:
6183class SomeClass: 6216class SomeClass:
6184 6217
6185 def __init__(self, arg, kwarg=1): 6218 def __init__(self, arg, kwarg=1):
6219
6186 def filter(self, nums): 6220 def filter(self, nums):
6187 def __str__(self):")))) 6221
6222 def __str__(self):
6223"))))
6188 (or enabled (hs-minor-mode -1))))) 6224 (or enabled (hs-minor-mode -1)))))
6189 6225
6190(ert-deftest python-hideshow-hide-levels-2 () 6226(ert-deftest python-hideshow-hide-levels-2 ()
@@ -6230,6 +6266,165 @@ class SomeClass:
6230")))) 6266"))))
6231 (or enabled (hs-minor-mode -1))))) 6267 (or enabled (hs-minor-mode -1)))))
6232 6268
6269(ert-deftest python-hideshow-hide-levels-3 ()
6270 "Should hide all blocks."
6271 (python-tests-with-temp-buffer
6272 "
6273def f():
6274 if 0:
6275 l = [i for i in range(5)
6276 if i < 3]
6277 abc = o.match(1, 2, 3)
6278
6279def g():
6280 pass
6281"
6282 (hs-minor-mode 1)
6283 (hs-hide-level 1)
6284 (should
6285 (string=
6286 (python-tests-visible-string)
6287 "
6288def f():
6289
6290def g():
6291"))))
6292
6293(ert-deftest python-hideshow-hide-levels-4 ()
6294 "Should hide 2nd level block."
6295 (python-tests-with-temp-buffer
6296 "
6297def f():
6298 if 0:
6299 l = [i for i in range(5)
6300 if i < 3]
6301 abc = o.match(1, 2, 3)
6302
6303def g():
6304 pass
6305"
6306 (hs-minor-mode 1)
6307 (hs-hide-level 2)
6308 (should
6309 (string=
6310 (python-tests-visible-string)
6311 "
6312def f():
6313 if 0:
6314
6315def g():
6316 pass
6317"))))
6318
6319(ert-deftest python-hideshow-hide-all-1 ()
6320 "Should hide all blocks."
6321 (python-tests-with-temp-buffer
6322 "if 0:
6323
6324 aaa
6325 l = [i for i in range(5)
6326 if i < 3]
6327 ccc
6328 abc = o.match(1, 2, 3)
6329 ddd
6330
6331def f():
6332 pass
6333"
6334 (hs-minor-mode 1)
6335 (hs-hide-all)
6336 (should
6337 (string=
6338 (python-tests-visible-string)
6339 "if 0:
6340
6341def f():
6342"))))
6343
6344(ert-deftest python-hideshow-hide-all-2 ()
6345 "Should hide comments."
6346 (python-tests-with-temp-buffer
6347 "
6348# Multi line
6349# comment
6350
6351\"\"\"
6352# Multi line
6353# string
6354\"\"\"
6355"
6356 (hs-minor-mode 1)
6357 (hs-hide-all)
6358 (should
6359 (string=
6360 (python-tests-visible-string)
6361 "
6362# Multi line
6363
6364\"\"\"
6365# Multi line
6366# string
6367\"\"\"
6368"))))
6369
6370(ert-deftest python-hideshow-hide-all-3 ()
6371 "Should not hide comments when `hs-hide-comments-when-hiding-all' is nil."
6372 (python-tests-with-temp-buffer
6373 "
6374# Multi line
6375# comment
6376
6377\"\"\"
6378# Multi line
6379# string
6380\"\"\"
6381"
6382 (hs-minor-mode 1)
6383 (let ((hs-hide-comments-when-hiding-all nil))
6384 (hs-hide-all))
6385 (should
6386 (string=
6387 (python-tests-visible-string)
6388 "
6389# Multi line
6390# comment
6391
6392\"\"\"
6393# Multi line
6394# string
6395\"\"\"
6396"))))
6397
6398(ert-deftest python-hideshow-hide-block-1 ()
6399 "Should hide current block."
6400 (python-tests-with-temp-buffer
6401 "
6402if 0:
6403
6404 aaa
6405 l = [i for i in range(5)
6406 if i < 3]
6407 ccc
6408 abc = o.match(1, 2, 3)
6409 ddd
6410
6411def f():
6412 pass
6413"
6414 (hs-minor-mode 1)
6415 (python-tests-look-at "ddd")
6416 (forward-line)
6417 (hs-hide-block)
6418 (should
6419 (string=
6420 (python-tests-visible-string)
6421 "
6422if 0:
6423
6424def f():
6425 pass
6426"))))
6427
6233 6428
6234(ert-deftest python-tests--python-nav-end-of-statement--infloop () 6429(ert-deftest python-tests--python-nav-end-of-statement--infloop ()
6235 "Checks that `python-nav-end-of-statement' doesn't infloop in a 6430 "Checks that `python-nav-end-of-statement' doesn't infloop in a