SiteMap Search ElispArea HowTo Glossary RecentChanges News Problems Suggestions
Cameroon, National Day

UniversialCloseParen

Difference between revision 7 and current revision

Summary: Rollback to 2008-09-05 00:16 UTC

No diff available.

I prefer not to have the close paren inserted automatically when I type the open paren, so I did some experiment to see how to compute the correct type of close paren from the rightmost open-but-unclosed paren. Note that the content after point is not considered.

Try to put the point after “({()[(” and hit the close key a couple times to see the effect. I note that there is still some issues with escaping.

(global-set-key "]" 'universal-close-paren)
(defvar ucp-open-re
  "[[({]"
  "Regexp matching open raw parens")
(defvar ucp-close-re
  "[])}]"                               ;close bracket must come first
  "Regexp matching close raw parens.")
(defun ucp-lp2rp (lhs)
  (cond ((string-equal lhs "(") ")")    ;cannot use case because of eql
	((string-equal lhs "{") "}")
	((string-equal lhs "[") "]")
	((string-equal lhs "\\(") "\\)")
	((string-equal lhs "\\{") "\\}")
	((string-equal lhs "\\[") "\\]")
	(t (concat "ucp-lp2rp unexpected: " lhs))))
(defun ucp-rp2lp (rhs)
  (cond ((string-equal rhs ")") "(")
	((string-equal rhs "}") "{")
	((string-equal rhs "]") "[")
	((string-equal rhs "\\)") "\\(")
	((string-equal rhs "\\}") "\\{")
	((string-equal rhs "\\]") "\\[")
	(t (concat "ucp-rp2lp unexpected: " rhs))))
(defun universal-close-paren (parg)
  "Insert the \"correct\" close paren.
Use C-u to override."
  (interactive "P")
  (if parg
    (self-insert-command 1)             ;override
    (let ((rightmost-paren
	   (save-excursion (save-match-data (ucp-pda 0 "")))))
      (if (= (car rightmost-paren) 0)
	;; problem detected
	(if (string-equal (cdr rightmost-paren) "")
	  ;; no open detected
	  (message "Parens are already balanced up to this point.")
	  ;; extra close detected: long else
	  (goto-char (string-to-int (cdr rightmost-paren)))
	  (message "Extra close paren detected here."))
	;; car is point of interest
	(if (string-match ucp-open-re (cdr rightmost-paren))
	  ;; match: we want to insert rp
	  (let ((rp (ucp-lp2rp (cdr rightmost-paren))))
	    ;; detect if we have already manually escaped by ourselves
	    (if (and
		 (string-equal (substring rp 0 1) "\\") ;rp is escaped
		 (= (preceding-char) ?\\) ;point is not right after \\
		 (/= (char-before (1- (point))) ?\\))
	      (insert (substring rp 1)) ;skip first char
	      (insert rp)))             ;no skip
	  ;; mismatch detected: long else
	  (goto-char (car rightmost-paren))
	  (message
	   (concat "Mismatched open paren detected here. Expecting: "
		   (ucp-rp2lp (cdr rightmost-paren)))))))))
(defun ucp-pda (level expected-open)  ;return point of rightmost unclosed open
  "Use the recursion stack to perform paren matching.
May run into limits for large files. Try something like:
\(setq max-lisp-eval-depth \(* 64 1048576\)\)
\(setq max-specpdl-size \(* 64 1048576\)\)

LEVEL is the number of close paren we have seen so far.
EXPECTED-OPEN is what we are expected to see if we do see an open paren.

On non-zero level, return nil when match found.
Otherwise, return
  (0.\"\") if goes pass point-min
  (0.\"point\") if extra close detected at point
  (point.\"open\") if point is the matched open
  (point.\"close\") if open at point mismatches close"
  (let ((rightmost-paren
	 (and (re-search-backward
	       (concat "\\(" ucp-open-re "\\|" ucp-close-re "\\)")
	       (point-min) 't)
	      (cons (point) (match-string 0)))))
    (if (not rightmost-paren)
      ;; no more paren
      (cons 0 (if (= level 0)           ;walk passed point-min
		""                      ;=> 0.""
		t))                     ;=> 0.t (t will be replaced by oldpt)
      ;; hit a paren, long else
      (when (re-search-backward "\\\\" (- (point) 1) t)
	(setq rightmost-paren           ;patch rightmost-paren if quoted
	      (cons (point) (concat "\\" (cdr rightmost-paren)))))
      (let ((p (cdr rightmost-paren)))
	(if (string-match ucp-close-re p) ;note the patch above
	  ;; hit a close paren
	  (let ((oldpt (point))         ;at the closing paren
		(recur                  ;eat until the matching open
		 (ucp-pda (1+ level) (ucp-rp2lp p))))
	    (if recur
	      ;; not nil => mismatch and report point
	      (if (= (car recur) 0)
		(cons 0 (number-to-string oldpt)) ;0.t => 0."oldpt"
		;; if pt.nil => pt."close", otherwise => keep
		(if (cdr recur) recur (cons (point) p)))
	      ;; nil => match, restart scanning at same level
	      (ucp-pda level expected-open)))
	  ;; hit an open paren
	  (if (= level 0)
	    rightmost-paren             ;=>pt.open; found open at level 0!
	    (if (string-equal expected-open p)
	      nil                       ;match => nil
	      (cons (point) nil)))))))) ;miss => pt.nil (nil will be replaced)
			    ;but note: nil will become (lp2rp expected-open)

I like the idea, but I think the code could be simpler. Does the following work for you? :)

  (defconst all-paren-syntax-table
    (let ((table (make-syntax-table)))
      (modify-syntax-entry ?{ "(}" table)
      (modify-syntax-entry ?} "){" table)
      (modify-syntax-entry ?\( "()" table)
      (modify-syntax-entry ?\) ")(" table)
      (modify-syntax-entry ?\[ "(]" table)
      (modify-syntax-entry ?\] ")[" table)
      table)
    "A syntax table giving all parenthesis parenthesis syntax.")
  (defun close-open-paren ()
    (interactive)
    (with-syntax-table all-paren-syntax-table
      (insert (save-excursion (up-list -1) (matching-paren (char-after))))))

nschum

Not really, but I appreciate from your code that I never thought about cooking up a syntax table and then use with-syntax-table. I think this idea can indeed clean up the code a little bit in the “paren detection” in the ucp-pda function. But the escape issue still has to be handled (I wanted “\{” to close with “\}” as a LaTeX user), as well as the error reporting when mismatch has been found (say when closing on “([{]”). One thing I am still not happy with ucp is that it doesn’t understand “\\[” should be closed with “]” since the escape has been escaped. But that’s something I will fix later. – MaverickWoo

Ah, I see. I didn’t really read through all that code and just assumed, you didn’t want them to be closed … :) The quoting is added easily enough, but I wouldn’t know how to catch those errors.

  (defconst all-paren-syntax-table2
    (let ((table (make-syntax-table)))
      (modify-syntax-entry ?{ "(}" table)
      (modify-syntax-entry ?} "){" table)
      (modify-syntax-entry ?\( "()" table)
      (modify-syntax-entry ?\) ")(" table)
      (modify-syntax-entry ?\[ "(]" table)
      (modify-syntax-entry ?\] ")[" table)
      (modify-syntax-entry ?\\ "'" table)
      table)
    "A syntax table giving all parenthesis parenthesis syntax.")
  (defun close-quoted-open-paren ()
    (interactive)
    (let (pos closing)
      (with-syntax-table all-paren-syntax-table2
        (setq pos (save-excursion (up-list -1) (point)))
        (setq closing (matching-paren (char-after pos))))
      (and (eq (char-syntax (char-before pos)) ?\\)
           (not (eq (char-syntax (char-before (1- pos))) ?\\))
           (insert (char-before pos)))
      (insert closing)))