From: Alan Mackenzie <acm@muc.de>
To: Stefan Monnier <monnier@iro.umontreal.ca>
Cc: emacs-devel@gnu.org
Subject: Re: Proposed extension of show-paren-mode: Highlight parens when point is in L or R margin.
Date: Thu, 16 Oct 2014 15:46:10 +0000 [thread overview]
Message-ID: <20141016154610.GF3421@acm.acm> (raw)
In-Reply-To: <jwvvbnkrsux.fsf-monnier+emacs@gnu.org>
Hello, Stefan.
On Thu, Oct 16, 2014 at 10:37:56AM -0400, Stefan Monnier wrote:
> >> > (defface show-paren-mismatch
> >> > '((((class color)) (:foreground "white" :background "purple"))
> >> > (t (:inverse-video t)))
> >> > "Face used for a mismatching paren."
> >> > :group 'paren-showing-faces) <-------- paren
> >> > ^
> >> > |
> >> > point
> >> Ah, so now your periphery extends to the next line.
> > No, no, no! By the two line arrow symbol, I meant point somewhere before
> > ":group".
> Oh, I see. That seems even more weird to me. I think this one is
> pushing things a bit too far for my taste. I think we should stick to
> parens that are separated from point only by whitespace (or something
> semantically equivalent such as comments).
I've been trying this out the past few days, and it seems having a
closing paren at EOL shown when point is in LH periphery is quite useful.
On the other hand, with point at EOL showing a paren at LH core, as you
type (lisp), various parens flash on and off like a third rate web site,
and it quickly gets irritating. So I commented that possibility out.
What is still there is an open paren direcly left of point flashing just
after you've typed it (e.g. with M-:). This irritates somewhat, and
maybe this should come out, too.
> And of course, it gets you an ambiguity for
> (defun foo ()
> (bar))
> ^
> |
> point
The nearest eligible paren to point currently takes priority.
> >> That means you get an ambiguity for ....
> > Oh no I don't!
> Just to be clear: ambiguities aren't that bad (we have them already
> when point is between a close and an open paren).
OK.
> >> >> If so, I guess we should also do it in cases such as:
> >> >> foo(blabla<point>);
> >> > Hmmm. Maybe.
> >> OK, sounds good. There have already been requests for that kind of feature.
> > OK. How about showing the paren (and its match) when
> > (i) point is touching the paren, and is either outside it (current
> > implementation), or inside it (new feature), with an "outside" taking
> > prioirty;
> Sounds good. That's the feature requested in
> http://stackoverflow.com/questions/25648067/emacs-matching-parenthesis-when-cursor-is-on-closing-parenthesis
> > (ii) point is in the periphery and there is a paren at an extreme edge
> > of the core, one at the nearest (to point) edge taking priority (already
> > implemented)?
> I think we should only consider the extreme edge of the core that's
> closest to point.
As I said above, currently I've got that for point in RH periphery, but
not LH periphery.
I've refactored the code somewhat, too. I've now got
show-paren--locate-near-paren, as promised, but I've put the "not
escaped" test into this defun, too. That way, if there're two candidate
parens, as in
?\)<point>)
, the code doesn't just give up when the first candidate is found to be
escaped. I've also renamed variables from "oldpos" to "outside", since
the original name had become somewhat obscure. show-paren--default has
become a little shorter.
A question: show-paren--default returns a list containe here-beg,
here-end, there-beg, and there-end, four positions. Couldn't we just
return two of these t?here-beg (renamed just to t?here), saving a bit of
messing around, or are there circumstances where here-end might not be
(1+ here-beg)?
See what you think.
=== modified file 'lisp/paren.el'
--- lisp/paren.el 2014-02-10 01:34:22 +0000
+++ lisp/paren.el 2014-10-16 15:10:34 +0000
@@ -72,12 +72,20 @@
:group 'paren-showing
:version "20.3")
+(defcustom show-paren-when-point-in-periphery nil
+ "If non-nil, show parens when point is in the line's periphery.
+The periphery is at the beginning or end of a line or in any
+whitespace there."
+ :type 'boolean
+ :group 'paren-showing
+ :version "25.1")
+
(define-obsolete-face-alias 'show-paren-match-face 'show-paren-match "22.1")
(define-obsolete-face-alias 'show-paren-mismatch-face
'show-paren-mismatch "22.1")
-(defvar show-paren-highlight-openparen t
+(defcustom show-paren-highlight-openparen t
"Non-nil turns on openparen highlighting when matching forward.")
(defvar show-paren--idle-timer nil)
@@ -112,76 +120,120 @@
(delete-overlay show-paren--overlay)
(delete-overlay show-paren--overlay-1)))
+(defun show-paren--categorize-paren (pos)
+ "Determine whether the character after POS has paren syntax,
+and if so, return a cons (DIR . OUTSIDE). For an open paren, DIR
+is 1 and OUTSIDE is the position before the paren. For a close
+paren, DIR is -1 and OUTSIDE is the position after the paren. If
+the character isn't a paren, return nil."
+ (cond
+ ((eq (syntax-class (syntax-after pos)) 4)
+ (cons 1 pos))
+ ((eq (syntax-class (syntax-after pos)) 5)
+ (cons -1 (1+ pos)))
+ (t nil)))
+
+(defun show-paren--unescaped-p (p-cons)
+ "Is the paren in P-CONS unescaped? If so return P-CONS, else nil.
+P-CONS is either nil or a cons (DIR . OUTSIDE) where DIR is 1 for
+an open paren, -1 for a close paren, and OUTSIDE is the buffer
+position of the outside of the paren."
+ (when p-cons
+ (save-excursion
+ (goto-char (cdr p-cons))
+ (if (= (car p-cons) -1) (backward-char))
+ (when (= (logand (skip-syntax-backward "/\\") 1) 0)
+ p-cons))))
+
+(defun show-paren--locate-near-paren ()
+ "Locate an unescaped paren \"near\" point to show.
+If one is found, return the cons (DIR . OUTSIDE), where DIR is 1
+for an open paren, -1 for a close paren, and OUTSIDE is the buffer
+position of the outside of the paren. Otherwise return nil."
+ (let* ((ind-pos (save-excursion (back-to-indentation) (point)))
+ (eol-pos
+ (save-excursion
+ (end-of-line) (skip-chars-backward " \t" ind-pos) (point)))
+ (before (show-paren--categorize-paren (1- (point))))
+ (after (show-paren--categorize-paren (point))))
+ (cond
+ ;; Point is immediately outside a paren.
+ ((and (eq (car before) -1) (show-paren--unescaped-p before)))
+ ((and (eq (car after) 1) (show-paren--unescaped-p after)))
+ ;; Point is immediately inside a paren.
+ ((show-paren--unescaped-p before))
+ ((show-paren--unescaped-p after))
+ ;; Point is in the whitespace before the code.
+ ((and show-paren-when-point-in-periphery
+ (< (point) ind-pos))
+ (or (show-paren--unescaped-p (show-paren--categorize-paren ind-pos))
+ (show-paren--unescaped-p (show-paren--categorize-paren (1- eol-pos)))))
+ ;; Point is in the whitespace after the code.
+ ((and show-paren-when-point-in-periphery
+ (>= (point) eol-pos))
+ (or (show-paren--unescaped-p (show-paren--categorize-paren (1- eol-pos)))
+ ;; (show-paren--unescaped-p (show-paren--categorize-paren ind-pos))
+ )))))
+
(defvar show-paren-data-function #'show-paren--default
- "Function to find the opener/closer at point and its match.
+ "Function to find the opener/closer \"near\" point and its match.
The function is called with no argument and should return either nil
-if there's no opener/closer at point, or a list of the form
+if there's no opener/closer near point, or a list of the form
\(HERE-BEG HERE-END THERE-BEG THERE-END MISMATCH)
-Where HERE-BEG..HERE-END is expected to be around point.")
+Where HERE-BEG..HERE-END is expected to be around the paren.")
(defun show-paren--default ()
- (let* ((oldpos (point))
- (dir (cond ((eq (syntax-class (syntax-after (1- (point)))) 5) -1)
- ((eq (syntax-class (syntax-after (point))) 4) 1)))
- (unescaped
- (when dir
- ;; Verify an even number of quoting characters precede the paren.
- ;; Follow the same logic as in `blink-matching-open'.
- (= (if (= dir -1) 1 0)
- (logand 1 (- (point)
- (save-excursion
- (if (= dir -1) (forward-char -1))
- (skip-syntax-backward "/\\")
- (point)))))))
- (here-beg (if (eq dir 1) (point) (1- (point))))
- (here-end (if (eq dir 1) (1+ (point)) (point)))
- pos mismatch)
+ (let* ((temp (show-paren--locate-near-paren))
+ (dir (car temp))
+ (outside (cdr temp))
+ pos mismatch here-beg here-end)
;;
;; Find the other end of the sexp.
- (when unescaped
- (save-excursion
- (save-restriction
- ;; Determine the range within which to look for a match.
- (when blink-matching-paren-distance
- (narrow-to-region
- (max (point-min) (- (point) blink-matching-paren-distance))
- (min (point-max) (+ (point) blink-matching-paren-distance))))
- ;; Scan across one sexp within that range.
- ;; Errors or nil mean there is a mismatch.
- (condition-case ()
- (setq pos (scan-sexps (point) dir))
- (error (setq pos t mismatch t)))
- ;; Move back the other way and verify we get back to the
- ;; starting point. If not, these two parens don't really match.
- ;; Maybe the one at point is escaped and doesn't really count,
- ;; or one is inside a comment.
- (when (integerp pos)
- (unless (condition-case ()
- (eq (point) (scan-sexps pos (- dir)))
- (error nil))
- (setq pos nil)))
- ;; If found a "matching" paren, see if it is the right
- ;; kind of paren to match the one we started at.
- (if (not (integerp pos))
- (if mismatch (list here-beg here-end nil nil t))
- (let ((beg (min pos oldpos)) (end (max pos oldpos)))
- (unless (eq (syntax-class (syntax-after beg)) 8)
- (setq mismatch
- (not (or (eq (char-before end)
- ;; This can give nil.
- (cdr (syntax-after beg)))
- (eq (char-after beg)
- ;; This can give nil.
- (cdr (syntax-after (1- end))))
- ;; The cdr might hold a new paren-class
- ;; info rather than a matching-char info,
- ;; in which case the two CDRs should match.
- (eq (cdr (syntax-after (1- end)))
- (cdr (syntax-after beg)))))))
- (list here-beg here-end
- (if (= dir 1) (1- pos) pos)
- (if (= dir 1) pos (1+ pos))
- mismatch))))))))
+ (when dir
+ (setq here-beg (if (eq dir 1) outside (1- outside))
+ here-end (if (eq dir 1) (1+ outside) outside))
+ (save-restriction
+ ;; Determine the range within which to look for a match.
+ (when blink-matching-paren-distance
+ (narrow-to-region
+ (max (point-min) (- (point) blink-matching-paren-distance))
+ (min (point-max) (+ (point) blink-matching-paren-distance))))
+ ;; Scan across one sexp within that range.
+ ;; Errors or nil mean there is a mismatch.
+ (condition-case ()
+ (setq pos (scan-sexps outside dir))
+ (error (setq pos t mismatch t)))
+ ;; Move back the other way and verify we get back to the
+ ;; starting point. If not, these two parens don't really match.
+ ;; Maybe the one at point is escaped and doesn't really count,
+ ;; or one is inside a comment.
+ (when (integerp pos)
+ (unless (condition-case ()
+ (eq outside (scan-sexps pos (- dir)))
+ (error nil))
+ (setq pos nil)))
+ ;; If found a "matching" paren, see if it is the right
+ ;; kind of paren to match the one we started at.
+ (if (not (integerp pos))
+ (if mismatch (list here-beg here-end nil nil t))
+ (let ((beg (min pos outside)) (end (max pos outside)))
+ (unless (eq (syntax-class (syntax-after beg)) 8)
+ (setq mismatch
+ (not (or (eq (char-before end)
+ ;; This can give nil.
+ (cdr (syntax-after beg)))
+ (eq (char-after beg)
+ ;; This can give nil.
+ (cdr (syntax-after (1- end))))
+ ;; The cdr might hold a new paren-class
+ ;; info rather than a matching-char info,
+ ;; in which case the two CDRs should match.
+ (eq (cdr (syntax-after (1- end)))
+ (cdr (syntax-after beg)))))))
+ (list here-beg here-end
+ (if (= dir 1) (1- pos) pos)
+ (if (= dir 1) pos (1+ pos))
+ mismatch)))))))
;; Find the place to show, if there is one,
;; and show it until input arrives.
@@ -215,7 +267,7 @@
;; Otherwise, turn off any such highlighting.
(if (or (not here-beg)
(and (not show-paren-highlight-openparen)
- (> here-end (point))
+ (= here-beg (point))
(integerp there-beg)))
(delete-overlay show-paren--overlay-1)
(move-overlay show-paren--overlay-1
@@ -234,7 +286,7 @@
(1- there-end) (1+ there-beg))))
(not (pos-visible-in-window-p closest)))))
(move-overlay show-paren--overlay
- (point)
+ (if (< there-beg here-beg) here-end here-beg)
(if (< there-beg here-beg) there-beg there-end)
(current-buffer))
(move-overlay show-paren--overlay
> Stefan
--
Alan Mackenzie (Nuremberg, Germany).
next prev parent reply other threads:[~2014-10-16 15:46 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-10-11 13:43 Proposed extension of show-paren-mode: Highlight parens when point is in L or R margin Alan Mackenzie
2014-10-11 14:21 ` Eli Zaretskii
2014-10-12 8:39 ` Alan Mackenzie
2014-10-12 8:55 ` David Kastrup
2014-10-12 9:25 ` Alan Mackenzie
2014-10-12 9:01 ` Eli Zaretskii
2014-10-12 10:18 ` Alan Mackenzie
2014-10-12 4:12 ` Stefan Monnier
2014-10-12 10:04 ` Alan Mackenzie
2014-10-14 17:49 ` Stefan Monnier
2014-10-14 18:32 ` John Yates
2014-10-15 9:12 ` Alan Mackenzie
2014-10-15 22:38 ` Andy Moreton
2014-10-16 2:43 ` Stefan Monnier
2014-10-16 9:53 ` Alan Mackenzie
2014-10-16 12:59 ` Stefan Monnier
2014-10-16 13:31 ` Alan Mackenzie
2014-10-16 14:37 ` Stefan Monnier
2014-10-16 15:46 ` Alan Mackenzie [this message]
2014-10-16 17:40 ` Stefan Monnier
2014-10-16 21:26 ` Alan Mackenzie
2014-10-17 0:14 ` Stefan Monnier
2014-10-14 21:50 ` João Távora
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20141016154610.GF3421@acm.acm \
--to=acm@muc.de \
--cc=emacs-devel@gnu.org \
--cc=monnier@iro.umontreal.ca \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).