From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED!not-for-mail From: Marcin Borkowski Newsgroups: gmane.emacs.bugs,gmane.emacs.devel Subject: bug#21072: Brave new mark-defun (and a testing tool) Date: Fri, 17 Feb 2017 09:54:51 +0100 Message-ID: <87wpcpw61w.fsf@jane> References: <87o9ydrzkr.fsf@mbork.pl> <87mvdriuss.fsf@mbork.pl> <87bmu6icea.fsf@mbork.pl> <87wpctgieu.fsf@mbork.pl> <52e67f43-edcf-09e3-5fd6-6079763fd234@yandex.ru> <87tw7wh9sf.fsf@mbork.pl> <87k28sdka6.fsf@jane> <87efyze00g.fsf@jane> <87bmu2eoji.fsf@jane> NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: blaine.gmane.org 1487322817 1595 195.159.176.226 (17 Feb 2017 09:13:37 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Fri, 17 Feb 2017 09:13:37 +0000 (UTC) User-Agent: mu4e 0.9.19; emacs 26.0.50.5 Cc: 21072@debbugs.gnu.org, emacs-devel@gnu.org To: Stefan Monnier Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Fri Feb 17 10:13:32 2017 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by blaine.gmane.org with esmtp (Exim 4.84_2) (envelope-from ) id 1ceeba-0008Ea-OH for geb-bug-gnu-emacs@m.gmane.org; Fri, 17 Feb 2017 10:13:31 +0100 Original-Received: from localhost ([::1]:52338 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ceebg-00033s-DR for geb-bug-gnu-emacs@m.gmane.org; Fri, 17 Feb 2017 04:13:36 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:50043) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ceeJm-0002kC-U4 for bug-gnu-emacs@gnu.org; Fri, 17 Feb 2017 03:55:14 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1ceeJj-0000M8-K0 for bug-gnu-emacs@gnu.org; Fri, 17 Feb 2017 03:55:06 -0500 Original-Received: from debbugs.gnu.org ([208.118.235.43]:44827) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1ceeJj-0000M3-G8 for bug-gnu-emacs@gnu.org; Fri, 17 Feb 2017 03:55:03 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1ceeJj-0001eG-3P for bug-gnu-emacs@gnu.org; Fri, 17 Feb 2017 03:55:03 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Marcin Borkowski Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Fri, 17 Feb 2017 08:55:03 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 21072 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: Original-Received: via spool by 21072-submit@debbugs.gnu.org id=B21072.14873216666287 (code B ref 21072); Fri, 17 Feb 2017 08:55:03 +0000 Original-Received: (at 21072) by debbugs.gnu.org; 17 Feb 2017 08:54:26 +0000 Original-Received: from localhost ([127.0.0.1]:43026 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1ceeJ7-0001dK-Qp for submit@debbugs.gnu.org; Fri, 17 Feb 2017 03:54:26 -0500 Original-Received: from mail.mojserwer.eu ([195.110.48.8]:35283) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1ceeJ5-0001dB-Ho for 21072@debbugs.gnu.org; Fri, 17 Feb 2017 03:54:24 -0500 Original-Received: from localhost (localhost [127.0.0.1]) by mail.mojserwer.eu (Postfix) with ESMTP id B3C0DE6AA3; Fri, 17 Feb 2017 09:54:20 +0100 (CET) X-Virus-Scanned: Debian amavisd-new at mail.mojserwer.eu Original-Received: from mail.mojserwer.eu ([127.0.0.1]) by localhost (mail.mojserwer.eu [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 1BboqJRiXTKw; Fri, 17 Feb 2017 09:54:16 +0100 (CET) Original-Received: from localhost (unknown [83.13.149.242]) by mail.mojserwer.eu (Postfix) with ESMTPSA id 84D48E62C2; Fri, 17 Feb 2017 09:54:16 +0100 (CET) In-reply-to: X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 208.118.235.43 X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Original-Sender: "bug-gnu-emacs" Xref: news.gmane.org gmane.emacs.bugs:129446 gmane.emacs.devel:212432 Archived-At: --=-=-= Content-Type: text/plain On 2017-02-16, at 14:22, Stefan Monnier wrote: >> Understood. Do you have then any better idea for the name of this >> function? beginning-of-defun--incomment-line-p seems to specific, >> in-comment-line-p _may_ be indeed too general. > > I'll let someone else decide if it deserves a "non-prefixed" name, but > as for the name after the potential prefix, I think focusing on > "comment" is the wrong idea. Maybe `insignificant-line-p`? Or `emptyish-line-p`? OK, so I have renamed it and expanded the docstring. I attach a corrected patch (the second one, the first one is the same as before). Is there anything else I can do before we may apply this patch and consider bug#21072 fixed? (Notice that three places could be still corrected: two when bug#24427 is fixed and possibly another one when the strange behavior of (beginning-of-defun 0) is fixed - I will officially file a bug about it later. But these apparently will have to wait.) Best, -- Marcin Borkowski --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0002-Fix-bug-21072-and-rework-mark-defun.patch >From 618217607d0bfc7ed8d4090afabea040088a0951 Mon Sep 17 00:00:00 2001 From: Marcin Borkowski Date: Tue, 14 Feb 2017 11:37:08 +0100 Subject: [PATCH] Fix bug#21072 and rework `mark-defun' * test/lisp/progmodes/elisp-mode-tests.el (mark-defun-test-buffer): New variable (mark-defun-no-arg-region-inactive) (mark-defun-no-arg-region-active) (mark-defun-arg-region-active) (mark-defun-pos-arg-region-inactive) (mark-defun-neg-arg-region-inactive, mark-defun-bob): Add tests for the new `mark-defun' * lisp/emacs-lisp/lisp.el (in-emptyish-line-p): New function (beginning-of-defun-comments): New function (mark-defun): Fix bug#21072, also rewrite large parts of `mark-defun' to accept a numerical prefix argument --- lisp/emacs-lisp/lisp.el | 132 ++++++++++++----- test/lisp/progmodes/elisp-mode-tests.el | 245 ++++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+), 38 deletions(-) diff --git a/lisp/emacs-lisp/lisp.el b/lisp/emacs-lisp/lisp.el index 0172e3af26..28b136eba4 100644 --- a/lisp/emacs-lisp/lisp.el +++ b/lisp/emacs-lisp/lisp.el @@ -398,6 +398,34 @@ beginning-of-defun-raw (goto-char (if arg-+ve floor ceiling)) nil)))))))) +(defun in-emptyish-line-p () + "Return non-nil if the point is in an \"emptyish\" line. +This means a line that consists entirely of comments and/or +whitespace." +;; See http://lists.gnu.org/archive/html/help-gnu-emacs/2016-08/msg00141.html + (save-excursion + (forward-line 0) + (< (line-end-position) + (let ((ppss (syntax-ppss))) + (when (nth 4 ppss) + (goto-char (nth 8 ppss))) + (forward-comment (point-max)) + (point))))) + +(defun beginning-of-defun-comments (&optional arg) + "Move to the beginning of ARGth defun, including comments." + (interactive "^p") + (unless arg (setq arg 1)) + (beginning-of-defun arg) + (let (nbobp) + (while (progn + (setq nbobp (zerop (forward-line -1))) + (and (not (looking-at "^\\s-*$")) + (in-emptyish-line-p) + nbobp))) + (when nbobp + (forward-line 1)))) + (defvar end-of-defun-function (lambda () (forward-sexp 1)) "Function for `end-of-defun' to call. @@ -478,48 +506,76 @@ end-of-defun (funcall end-of-defun-function) (funcall skip))))) -(defun mark-defun (&optional allow-extend) +(defun mark-defun (&optional arg) "Put mark at end of this defun, point at beginning. The defun marked is the one that contains point or follows point. +With positive ARG, mark this and that many next defuns; with negative +ARG, change the direction of marking. -Interactively, if this command is repeated -or (in Transient Mark mode) if the mark is active, -it marks the next defun after the ones already marked." +If the mark is active, it marks the next or previous defun(s) after +the one(s) already marked." (interactive "p") - (cond ((and allow-extend - (or (and (eq last-command this-command) (mark t)) - (and transient-mark-mode mark-active))) - (set-mark - (save-excursion - (goto-char (mark)) - (end-of-defun) - (point)))) - (t - (let ((opoint (point)) - beg end) - (push-mark opoint) - ;; Try first in this order for the sake of languages with nested - ;; functions where several can end at the same place as with - ;; the offside rule, e.g. Python. - (beginning-of-defun) - (setq beg (point)) - (end-of-defun) - (setq end (point)) - (while (looking-at "^\n") - (forward-line 1)) - (if (> (point) opoint) - (progn - ;; We got the right defun. - (push-mark beg nil t) - (goto-char end) - (exchange-point-and-mark)) - ;; beginning-of-defun moved back one defun - ;; so we got the wrong one. - (goto-char opoint) - (end-of-defun) - (push-mark (point) nil t) - (beginning-of-defun)) - (re-search-backward "^\n" (- (point) 1) t))))) + (setq arg (or arg 1)) + ;; There is no `mark-defun-back' function - see + ;; https://lists.gnu.org/archive/html/bug-gnu-emacs/2016-11/msg00079.html + ;; for explanation + (when (eq last-command 'mark-defun-back) + (setq arg (- arg))) + (when (< arg 0) + (setq this-command 'mark-defun-back)) + (cond ((use-region-p) + (if (>= arg 0) + (set-mark + (save-excursion + (goto-char (mark)) + ;; change the dotimes below to (end-of-defun arg) once bug #24427 is fixed + (dotimes (_ignore arg) + (end-of-defun)) + (point))) + (beginning-of-defun-comments (- arg)))) + (t + (let ((opoint (point)) + beg end) + (push-mark opoint) + ;; Try first in this order for the sake of languages with nested + ;; functions where several can end at the same place as with the + ;; offside rule, e.g. Python. + (beginning-of-defun-comments) + (setq beg (point)) + (end-of-defun) + (setq end (point)) + (when (or (and (<= (point) opoint) + (> arg 0)) + (= beg (point-min))) ; we were before the first defun! + ;; beginning-of-defun moved back one defun so we got the wrong + ;; one. If ARG < 0, however, we actually want to go back. + (goto-char opoint) + (end-of-defun) + (setq end (point)) + (beginning-of-defun-comments) + (setq beg (point))) + (goto-char beg) + (cond ((> arg 0) + ;; change the dotimes below to (end-of-defun arg) once bug #24427 is fixed + (dotimes (_ignore arg) + (end-of-defun)) + (setq end (point)) + (push-mark end nil t) + (goto-char beg)) + (t + (goto-char beg) + (unless (= arg -1) ; beginning-of-defun behaves + ; strange with zero arg - see + ; https://lists.gnu.org/archive/html/bug-gnu-emacs/2017-02/msg00196.html + (beginning-of-defun (1- (- arg)))) + (push-mark end nil t)))))) + (let (nbobp) + (while (progn + (setq nbobp (zerop (forward-line -1))) + (and (looking-at "^\\s-*$") + nbobp))) + (when nbobp + (forward-line 1)))) (defvar narrow-to-defun-include-comments nil "If non-nil, `narrow-to-defun' will also show comments preceding the defun.") diff --git a/test/lisp/progmodes/elisp-mode-tests.el b/test/lisp/progmodes/elisp-mode-tests.el index a00f6b1b43..2366e337df 100644 --- a/test/lisp/progmodes/elisp-mode-tests.el +++ b/test/lisp/progmodes/elisp-mode-tests.el @@ -711,5 +711,250 @@ elisp-tests-with-temp-buffer `(let ,marker-list ,@body)))) +;;; mark-defun + +(defvar mark-defun-test-buffer + ";; Comment header +=!before-1= +\(defun func-1 (arg) + =!inside-1=\"docstring\" + body) +=!after-1==!before-2= +;; Comment before a defun +\(d=!inside-2=efun func-2 (arg) + \"docstring\" + body) +=!after-2==!before-3= +\(defun func-3 (arg) + \"docstring\"=!inside-3= + body) +=!after-3==!before-4=(defun func-4 (arg) + \"docstring\"=!inside-4= + body) +=!after-4= +;; end +" + "Test buffer for `mark-defun'.") + +(ert-deftest mark-defun-no-arg-region-inactive () + "Test `mark-defun' with no prefix argument and inactive +region." + (setq last-command nil) + (elisp-tests-with-temp-buffer + mark-defun-test-buffer + ;; mark-defun inside a defun, with comments and an empty line + ;; before + (goto-char inside-1) + (mark-defun) + (should (= (point) before-1)) + (should (= (mark) after-1)) + ;; mark-defun inside a defun with comments before + (deactivate-mark) + (goto-char inside-2) + (mark-defun) + (should (= (point) before-2)) + (should (= (mark) after-2)) + ;; mark-defun inside a defun with empty line before + (deactivate-mark) + (goto-char inside-3) + (mark-defun) + (should (= (point) before-3)) + (should (= (mark) after-3)) + ;; mark-defun inside a defun with another one right before + (deactivate-mark) + (goto-char inside-4) + (mark-defun) + (should (= (point) before-4)) + (should (= (mark) after-4)) + ;; mark-defun between a comment and a defun + (deactivate-mark) + (goto-char before-1) + (mark-defun) + (should (= (point) before-1)) + (should (= (mark) after-1)) + ;; mark-defun between defuns + (deactivate-mark) + (goto-char before-3) + (mark-defun) + (should (= (point) before-3)) + (should (= (mark) after-3)) + ;; mark-defun in comment right before the defun + (deactivate-mark) + (goto-char before-2) + (mark-defun) + (should (= (point) before-2)) + (should (= (mark) after-2)))) + +(ert-deftest mark-defun-no-arg-region-active () + "Test `mark-defun' with no prefix argument and active +region." + (setq last-command nil) + (elisp-tests-with-temp-buffer + mark-defun-test-buffer + ;; mark-defun when a defun is marked + (goto-char before-1) + (set-mark after-1) + (mark-defun) + (should (= (point) before-1)) + (should (= (mark) after-2)) + ;; mark-defun when two defuns are marked + (deactivate-mark) + (goto-char before-1) + (set-mark after-2) + (mark-defun) + (should (= (point) before-1)) + (should (= (mark) after-3)))) + +(ert-deftest mark-defun-arg-region-active () + "Test `mark-defun' with a prefix arg and active region." + (setq last-command nil) + (elisp-tests-with-temp-buffer + mark-defun-test-buffer + ;; mark-defun with positive arg when a defun is marked + (goto-char before-1) + (set-mark after-1) + (mark-defun 2) + (should (= (point) before-1)) + (should (= (mark) after-3)) + ;; mark-defun with arg=-1 when a defun is marked + (goto-char before-2) + (set-mark after-2) + (mark-defun -1) + (should (= (point) before-1)) + (should (= (mark) after-2)) + ;; mark-defun with arg=-2 when a defun is marked + (goto-char before-3) + (set-mark after-3) + (mark-defun -2) + (should (= (point) before-1)) + (should (= (mark) after-3)))) + +(ert-deftest mark-defun-pos-arg-region-inactive () + "Test `mark-defun' with positive argument and inactive + region." + (setq last-command nil) + (elisp-tests-with-temp-buffer + mark-defun-test-buffer + ;; mark-defun with positive arg inside a defun + (goto-char inside-1) + (mark-defun 2) + (should (= (point) before-1)) + (should (= (mark) after-2)) + ;; mark-defun with positive arg between defuns + (deactivate-mark) + (goto-char before-3) + (mark-defun 2) + (should (= (point) before-3)) + (should (= (mark) after-4)) + ;; mark-defun with positive arg in a comment + (deactivate-mark) + (goto-char before-2) + (mark-defun 2) + (should (= (point) before-2)) + (should (= (mark) after-3)))) + +(ert-deftest mark-defun-neg-arg-region-inactive () + "Test `mark-defun' with negative argument and inactive + region." + (setq last-command nil) + (elisp-tests-with-temp-buffer + mark-defun-test-buffer + ;; mark-defun with arg=-1 inside a defun + (goto-char inside-1) + (mark-defun -1) + (should (= (point) before-1)) + (should (= (mark) after-1)) + ;; mark-defun with arg=-1 between defuns + (deactivate-mark) + (goto-char after-2) + (mark-defun -1) + (should (= (point) before-2)) + (should (= (mark) after-2)) + ;; mark-defun with arg=-1 in a comment + ;; (this is probably not an optimal behavior...) + (deactivate-mark) + (goto-char before-2) + (mark-defun -1) + (should (= (point) before-1)) + (should (= (mark) after-1)) + ;; mark-defun with arg=-2 inside a defun + (deactivate-mark) + (goto-char inside-4) + (mark-defun -2) + (should (= (point) before-3)) + (should (= (mark) after-4)) + ;; mark-defun with arg=-2 between defuns + (deactivate-mark) + (goto-char before-3) + (mark-defun -2) + (should (= (point) before-1)) + (should (= (mark) after-2))) + (elisp-tests-with-temp-buffer ; test case submitted by Drew Adams + "(defun a () + nil) +=!before-b=(defun b () +=!in-b= nil) +=!after-b=;;;; +\(defun c () + nil) +" + (setq last-command nil) + (goto-char in-b) + (mark-defun -1) + (should (= (point) before-b)) + (should (= (mark) after-b)))) + +(ert-deftest mark-defun-bob () + "Test `mark-defun' at the beginning of buffer." + ;; Bob, comment, newline, defun + (setq last-command nil) + (elisp-tests-with-temp-buffer + ";; Comment at the bob +=!before= +\(defun func (arg)=!inside= + \"docstring\" + body) +=!after=" + (goto-char inside) + (mark-defun) + (should (= (point) before)) + (should (= (mark) after))) + ;; Bob, newline, comment, defun + (elisp-tests-with-temp-buffer + "=!before= +;; Comment before the defun +\(defun func (arg)=!inside= + \"docstring\" + body) +=!after=" + (goto-char inside) + (mark-defun) + (should (= (point) before)) + (should (= (mark) after))) + ;; Bob, comment, defun + (elisp-tests-with-temp-buffer + "=!before=;; Comment at the bob before the defun +\(defun func (arg)=!inside= + \"docstring\" + body) +=!after=" + (goto-char inside) + (mark-defun) + (should (= (point) before)) + (should (= (mark) after))) + ;; Bob, newline, comment, newline, defun + (elisp-tests-with-temp-buffer + " +;; Comment before the defun +=!before= +\(defun func (arg)=!inside= + \"docstring\" + body) +=!after=" + (goto-char inside) + (mark-defun) + (should (= (point) before)) + (should (= (mark) after)))) + (provide 'elisp-mode-tests) ;;; elisp-mode-tests.el ends here -- 2.11.1 --=-=-=--