diff --git a/lisp/ChangeLog b/lisp/ChangeLog index a02f9642e55..655ae574468 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,13 @@ +2015-02-07 Fabián Ezequiel Gallina + + Fix hideshow integration. (Bug#19761) + + * progmodes/python.el + (python-hideshow-forward-sexp-function): New function based on + Carlos Pita patch. + (python-mode): Make `hs-special-modes-alist` use it and initialize + the end regexp with the empty string to avoid skipping parens. + 2015-02-07 Fabián Ezequiel Gallina * progmodes/python.el (python-check-custom-command): Do not use diff --git a/lisp/progmodes/python.el b/lisp/progmodes/python.el index de251181c14..3399429538f 100644 --- a/lisp/progmodes/python.el +++ b/lisp/progmodes/python.el @@ -3957,6 +3957,17 @@ Interactively, prompt for symbol." nil nil symbol)))) (message (python-eldoc--get-doc-at-point symbol))) + +;;; Hideshow + +(defun python-hideshow-forward-sexp-function (arg) + "Python specific `forward-sexp' function for `hs-minor-mode'. +Argument ARG is ignored." + arg ; Shut up, byte compiler. + (python-nav-end-of-defun) + (unless (python-info-current-line-empty-p) + (backward-char))) + ;;; Imenu @@ -4693,11 +4704,16 @@ Arguments START and END narrow the buffer region to work on." (add-function :before-until (local 'eldoc-documentation-function) #'python-eldoc-function)) - (add-to-list 'hs-special-modes-alist - `(python-mode "^\\s-*\\(?:def\\|class\\)\\>" nil "#" - ,(lambda (_arg) - (python-nav-end-of-defun)) - nil)) + (add-to-list + 'hs-special-modes-alist + `(python-mode + "\\s-*\\(?:def\\|class\\)\\>" + ;; Use the empty string as end regexp so it doesn't default to + ;; "\\s)". This way parens at end of defun are properly hidden. + "" + "#" + python-hideshow-forward-sexp-function + nil)) (set (make-local-variable 'outline-regexp) (python-rx (* space) block-start)) diff --git a/test/ChangeLog b/test/ChangeLog index e0d4eeb323a..b1e21511d65 100644 --- a/test/ChangeLog +++ b/test/ChangeLog @@ -1,3 +1,12 @@ +2015-02-07 Fabián Ezequiel Gallina + + * automated/python-tests.el + (python-tests-visible-string): New function. + (python-parens-electric-indent-1) + (python-triple-quote-pairing): Fix indentation, move require calls. + (python-hideshow-hide-levels-1) + (python-hideshow-hide-levels-2): New tests. + 2015-02-07 Dmitry Gutov * automated/vc-tests.el (vc-test--working-revision): Fix diff --git a/test/automated/python-tests.el b/test/automated/python-tests.el index 672b05c39de..e5fcda95012 100644 --- a/test/automated/python-tests.el +++ b/test/automated/python-tests.el @@ -24,6 +24,11 @@ (require 'ert) (require 'python) +;; Dependencies for testing: +(require 'electric) +(require 'hideshow) + + (defmacro python-tests-with-temp-buffer (contents &rest body) "Create a `python-mode' enabled temp buffer with CONTENTS. BODY is code to be executed within the temp buffer. Point is @@ -104,6 +109,28 @@ STRING, it is skipped so the next STRING occurrence is selected." (call-interactively 'self-insert-command))) chars))) +(defun python-tests-visible-string (&optional min max) + "Return the buffer string excluding invisible overlays. +Argument MIN and MAX delimit the region to be returned and +default to `point-min' and `point-max' respectively." + (let* ((min (or min (point-min))) + (max (or max (point-max))) + (buffer (current-buffer)) + (buffer-contents (buffer-substring-no-properties min max)) + (overlays + (sort (overlays-in min max) + (lambda (a b) + (let ((overlay-end-a (overlay-end a)) + (overlay-end-b (overlay-end b))) + (> overlay-end-a overlay-end-b)))))) + (with-temp-buffer + (insert buffer-contents) + (dolist (overlay overlays) + (if (overlay-get overlay 'invisible) + (delete-region (overlay-start overlay) + (overlay-end overlay)))) + (buffer-substring-no-properties (point-min) (point-max))))) + ;;; Tests for your tests, so you can test while you test. @@ -4358,12 +4385,11 @@ def foo(a, b, c): ;;; Electricity (ert-deftest python-parens-electric-indent-1 () - (require 'electric) (let ((eim electric-indent-mode)) (unwind-protect (progn (python-tests-with-temp-buffer - " + " from django.conf.urls import patterns, include, url from django.contrib import admin @@ -4375,66 +4401,148 @@ urlpatterns = patterns('', url(r'^$', views.index ) " - (electric-indent-mode 1) - (python-tests-look-at "views.index") - (end-of-line) + (electric-indent-mode 1) + (python-tests-look-at "views.index") + (end-of-line) - ;; Inserting commas within the same line should leave - ;; indentation unchanged. - (python-tests-self-insert ",") - (should (= (current-indentation) 4)) + ;; Inserting commas within the same line should leave + ;; indentation unchanged. + (python-tests-self-insert ",") + (should (= (current-indentation) 4)) - ;; As well as any other input happening within the same - ;; set of parens. - (python-tests-self-insert " name='index')") - (should (= (current-indentation) 4)) + ;; As well as any other input happening within the same + ;; set of parens. + (python-tests-self-insert " name='index')") + (should (= (current-indentation) 4)) - ;; But a comma outside it, should trigger indentation. - (python-tests-self-insert ",") - (should (= (current-indentation) 23)) + ;; But a comma outside it, should trigger indentation. + (python-tests-self-insert ",") + (should (= (current-indentation) 23)) - ;; Newline indents to the first argument column - (python-tests-self-insert "\n") - (should (= (current-indentation) 23)) + ;; Newline indents to the first argument column + (python-tests-self-insert "\n") + (should (= (current-indentation) 23)) - ;; All this input must not change indentation - (indent-line-to 4) - (python-tests-self-insert "url(r'^/login$', views.login)") - (should (= (current-indentation) 4)) + ;; All this input must not change indentation + (indent-line-to 4) + (python-tests-self-insert "url(r'^/login$', views.login)") + (should (= (current-indentation) 4)) - ;; But this comma does - (python-tests-self-insert ",") - (should (= (current-indentation) 23)))) + ;; But this comma does + (python-tests-self-insert ",") + (should (= (current-indentation) 23)))) (or eim (electric-indent-mode -1))))) (ert-deftest python-triple-quote-pairing () - (require 'electric) (let ((epm electric-pair-mode)) (unwind-protect (progn (python-tests-with-temp-buffer - "\"\"\n" - (or epm (electric-pair-mode 1)) - (goto-char (1- (point-max))) - (python-tests-self-insert ?\") - (should (string= (buffer-string) - "\"\"\"\"\"\"\n")) - (should (= (point) 4))) + "\"\"\n" + (or epm (electric-pair-mode 1)) + (goto-char (1- (point-max))) + (python-tests-self-insert ?\") + (should (string= (buffer-string) + "\"\"\"\"\"\"\n")) + (should (= (point) 4))) (python-tests-with-temp-buffer - "\n" - (python-tests-self-insert (list ?\" ?\" ?\")) - (should (string= (buffer-string) - "\"\"\"\"\"\"\n")) - (should (= (point) 4))) + "\n" + (python-tests-self-insert (list ?\" ?\" ?\")) + (should (string= (buffer-string) + "\"\"\"\"\"\"\n")) + (should (= (point) 4))) (python-tests-with-temp-buffer - "\"\n\"\"\n" - (goto-char (1- (point-max))) - (python-tests-self-insert ?\") - (should (= (point) (1- (point-max)))) - (should (string= (buffer-string) - "\"\n\"\"\"\n")))) + "\"\n\"\"\n" + (goto-char (1- (point-max))) + (python-tests-self-insert ?\") + (should (= (point) (1- (point-max)))) + (should (string= (buffer-string) + "\"\n\"\"\"\n")))) (or epm (electric-pair-mode -1))))) + +;;; Hideshow support + +(ert-deftest python-hideshow-hide-levels-1 () + "Should hide all methods when called after class start." + (let ((enabled hs-minor-mode)) + (unwind-protect + (progn + (python-tests-with-temp-buffer + " +class SomeClass: + + def __init__(self, arg, kwarg=1): + self.arg = arg + self.kwarg = kwarg + + def filter(self, nums): + def fn(item): + return item in [self.arg, self.kwarg] + return filter(fn, nums) + + def __str__(self): + return '%s-%s' % (self.arg, self.kwarg) +" + (hs-minor-mode 1) + (python-tests-look-at "class SomeClass:") + (forward-line) + (hs-hide-level 1) + (should + (string= + (python-tests-visible-string) + " +class SomeClass: + + def __init__(self, arg, kwarg=1): + def filter(self, nums): + def __str__(self):")))) + (or enabled (hs-minor-mode -1))))) + +(ert-deftest python-hideshow-hide-levels-2 () + "Should hide nested methods and parens at end of defun." + (let ((enabled hs-minor-mode)) + (unwind-protect + (progn + (python-tests-with-temp-buffer + " +class SomeClass: + + def __init__(self, arg, kwarg=1): + self.arg = arg + self.kwarg = kwarg + + def filter(self, nums): + def fn(item): + return item in [self.arg, self.kwarg] + return filter(fn, nums) + + def __str__(self): + return '%s-%s' % (self.arg, self.kwarg) +" + (hs-minor-mode 1) + (python-tests-look-at "def fn(item):") + (hs-hide-block) + (should + (string= + (python-tests-visible-string) + " +class SomeClass: + + def __init__(self, arg, kwarg=1): + self.arg = arg + self.kwarg = kwarg + + def filter(self, nums): + def fn(item): + return filter(fn, nums) + + def __str__(self): + return '%s-%s' % (self.arg, self.kwarg) +")))) + (or enabled (hs-minor-mode -1))))) + + (provide 'python-tests)