diff options
| author | kobarity | 2022-08-25 14:29:10 +0200 |
|---|---|---|
| committer | Lars Ingebrigtsen | 2022-08-25 14:29:10 +0200 |
| commit | 7c7fc8fa5be69ccbfd2d134a899cdedf29d25aab (patch) | |
| tree | 19f958616988d8f04851d20838ffeefbab3613e7 | |
| parent | 74d0304ad446dbad6fed887784ffbc3d0fdb59fd (diff) | |
| download | emacs-7c7fc8fa5be69ccbfd2d134a899cdedf29d25aab.tar.gz emacs-7c7fc8fa5be69ccbfd2d134a899cdedf29d25aab.zip | |
Add Python blocks support for hideshow
* lisp/progmodes/python.el (python-nav-beginning-of-block-regexp):
New variable.
(python-hideshow-forward-sexp-function): Change to call
`python-nav-end-of-block'.
(python-hideshow-find-next-block): New function to be used as
FIND-NEXT-BLOCK-FUNC in `hs-special-modes-alist'.
(python-info-looking-at-beginning-of-block): New function to be
used as LOOKING-AT-BLOCK-START-P-FUNC in `hs-special-modes-alist'.
(python-mode): Change settings of `hs-special-modes-alist'.
* test/lisp/progmodes/python-tests.el
(python-hideshow-hide-levels-1): Fix to keep empty lines.
(python-info-looking-at-beginning-of-block-1)
(python-hideshow-hide-levels-3, python-hideshow-hide-levels-4)
(python-hideshow-hide-all-1, python-hideshow-hide-all-2)
(python-hideshow-hide-all-3, python-hideshow-hide-block-1): New
tests (bug#56635).
| -rw-r--r-- | lisp/progmodes/python.el | 57 | ||||
| -rw-r--r-- | test/lisp/progmodes/python-tests.el | 197 |
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." | |||
| 1524 | The name of the defun should be grouped so it can be retrieved | 1524 | The name of the defun should be grouped so it can be retrieved |
| 1525 | via `match-string'.") | 1525 | via `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'. |
| 1529 | With positive ARG search backwards, else search forwards." | 1533 | With 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'. |
| 4918 | Argument ARG is ignored." | 4922 | Argument 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'. | ||
| 4927 | Call `python-nav-forward-block' to find next block and check if | ||
| 4928 | block-start ends within MAXP. If COMMENTS is not nil, comments | ||
| 4929 | are 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 | " | ||
| 5736 | def f(): | ||
| 5737 | if True: | ||
| 5738 | pass | ||
| 5739 | l = [x * 2 | ||
| 5740 | for x in range(5) | ||
| 5741 | if x < 3] | ||
| 5742 | # if False: | ||
| 5743 | \"\"\" | ||
| 5744 | if 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: | |||
| 6183 | class SomeClass: | 6216 | class 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 | " | ||
| 6273 | def f(): | ||
| 6274 | if 0: | ||
| 6275 | l = [i for i in range(5) | ||
| 6276 | if i < 3] | ||
| 6277 | abc = o.match(1, 2, 3) | ||
| 6278 | |||
| 6279 | def g(): | ||
| 6280 | pass | ||
| 6281 | " | ||
| 6282 | (hs-minor-mode 1) | ||
| 6283 | (hs-hide-level 1) | ||
| 6284 | (should | ||
| 6285 | (string= | ||
| 6286 | (python-tests-visible-string) | ||
| 6287 | " | ||
| 6288 | def f(): | ||
| 6289 | |||
| 6290 | def g(): | ||
| 6291 | ")))) | ||
| 6292 | |||
| 6293 | (ert-deftest python-hideshow-hide-levels-4 () | ||
| 6294 | "Should hide 2nd level block." | ||
| 6295 | (python-tests-with-temp-buffer | ||
| 6296 | " | ||
| 6297 | def f(): | ||
| 6298 | if 0: | ||
| 6299 | l = [i for i in range(5) | ||
| 6300 | if i < 3] | ||
| 6301 | abc = o.match(1, 2, 3) | ||
| 6302 | |||
| 6303 | def g(): | ||
| 6304 | pass | ||
| 6305 | " | ||
| 6306 | (hs-minor-mode 1) | ||
| 6307 | (hs-hide-level 2) | ||
| 6308 | (should | ||
| 6309 | (string= | ||
| 6310 | (python-tests-visible-string) | ||
| 6311 | " | ||
| 6312 | def f(): | ||
| 6313 | if 0: | ||
| 6314 | |||
| 6315 | def 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 | |||
| 6331 | def 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 | |||
| 6341 | def 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 | " | ||
| 6402 | if 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 | |||
| 6411 | def 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 | " | ||
| 6422 | if 0: | ||
| 6423 | |||
| 6424 | def 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 |