From f52154007f41abe6857acab91e31ab4a7d18210d Mon Sep 17 00:00:00 2001 From: Stefan Monnier Date: Sun, 14 May 2000 00:56:10 +0000 Subject: [PATCH] (comment-start, comment-start-skip, comment-end): Made `defvar'. (comment-style): Extract the choice out of comment-styles. (comment-continue): Just a simple string now. (comment-normalize-vars): Update for the new comment-continue. (until, comment-end-quote-re): Removed. (comment-quote-re, comment-quote-nested): New functions for quoting. These quote both the end and the start and also work for single-chars. (comment-padright): Added lots of comments. (comment-padleft): Added more comments. Check comment-end rather than STR to determine whether N can be applied or not. (uncomment-region): Rename BLOCK to BOX. Use the new comment-quote-nested. Use only one marker and properly set it back to nil. Be more picky when eliminating continuation markers. --- lisp/newcomment.el | 383 ++++++++++++++++++++++++++------------------- 1 file changed, 224 insertions(+), 159 deletions(-) diff --git a/lisp/newcomment.el b/lisp/newcomment.el index 0507048ab4f..075573e4230 100644 --- a/lisp/newcomment.el +++ b/lisp/newcomment.el @@ -6,7 +6,7 @@ ;; Maintainer: Stefan Monnier ;; Keywords: comment uncomment ;; Version: $Name: $ -;; Revision: $Id: newcomment.el,v 1.6 1999/12/08 00:19:51 monnier Exp $ +;; Revision: $Id: newcomment.el,v 1.7 2000/05/13 19:41:08 monnier Exp $ ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by @@ -34,8 +34,6 @@ ;; - the code assumes that bol is outside of any comment/string. ;; - uncomment-region with a numeric argument can render multichar ;; comment markers invalid. -;; - comment-box in C with a numeric argument generates wrong end-of-line -;; continuation markers. ;;; Todo: @@ -45,20 +43,10 @@ ;; - uncomment-region with a consp (for blocks) or somehow make the ;; deletion of continuation markers less dangerous ;; - drop block-comment- unless it's really used -;; - uncomment-region on a part of a comment -;; - obey the numarg of uncomment-region for continuation markers +;; - uncomment-region on a subpart of a comment ;; - support gnu-style "multi-line with space in continue" -;; - document string-strip -;; - better document comment-continue (and probably turn it into a -;; simple string). -;; - don't overwrite comment-padding. -;; - better document comment-end-quote. Probably make it better -;; self-sufficient so that other quoting can be used. -;; - comment the padright/padleft. ;; - somehow allow comment-dwim to use the region even if transient-mark-mode ;; is not turned on. -;; - document comment-region-internal -;; - comment-quote-nested should quote both the start and end of comment. ;;; Code: @@ -73,7 +61,8 @@ "Non-nil if syntax-tables can be used instead of regexps. Can also be `undecided' which means that a somewhat expensive test will be used to try to determine whether syntax-tables should be trusted -to understand comments or not.") +to understand comments or not in the given buffer. +Major modes should set this variable.") (defcustom comment-column 32 "*Column to indent right-margin comments to. @@ -84,28 +73,20 @@ can set the value for a particular mode using that mode's hook." :group 'comment) (make-variable-buffer-local 'comment-column) -(defcustom comment-start nil - "*String to insert to start a new comment, or nil if no comment syntax." - :type '(choice (const :tag "None" nil) - string) - :group 'comment) +(defvar comment-start nil + "*String to insert to start a new comment, or nil if no comment syntax.") -(defcustom comment-start-skip nil +(defvar comment-start-skip nil "*Regexp to match the start of a comment plus everything up to its body. If there are any \\(...\\) pairs, the comment delimiter text is held to begin -at the place matched by the close of the first pair." - :type '(choice (const :tag "None" nil) - regexp) - :group 'comment) +at the place matched by the close of the first pair.") (defvar comment-end-skip nil "Regexp to match the end of a comment plus everything up to its body.") -(defcustom comment-end "" +(defvar comment-end "" "*String to insert to end a new comment. -Should be an empty string if comments are terminated by end-of-line." - :type 'string - :group 'comment) +Should be an empty string if comments are terminated by end-of-line.") (defvar comment-indent-hook nil "Obsolete variable for function to compute desired indentation for a comment. @@ -126,19 +107,22 @@ the comment's starting delimiter.") This should be locally set by each major mode if needed.") (defvar comment-continue nil - "Pair of strings to insert for multiline comments.") -(defvar comment-add 0 - "How many more chars should be inserted by default.") + "Continuation string to insert for multiline comments. +This string will be added at the beginning of each line except the very +first one when commenting a region with a commenting style that allows +comments to span several lines. +It should generally have the same length as `comment-start' in order to +preserve indentation. +If it is nil a value will be automatically derived from `comment-start' +by replacing its first character with a space.") + +(defvar comment-add 0 + "How many more comment chars should be inserted by `comment-region'. +This determines the default value of the numeric argument of `comment-region'. +This should generally stay 0, except for a few modes like Lisp where +it can be convenient to set it to 1 so that regions are commented with +two semi-colons.") -(defcustom comment-style 'plain - "*Style to be used for inserting comments." - :group 'comment - :type '(choice (const plain) - (const indent) - (const aligned) - (const multi-line) - (const extra-line) - (const box))) (defconst comment-styles '((plain . (nil nil nil nil)) (indent . (nil nil nil t)) @@ -146,12 +130,25 @@ This should be locally set by each major mode if needed.") (multi-line . (t nil nil t)) (extra-line . (t nil t t)) (box . (t t t t))) - "Possible styles. -(STYLE . (MULTI ALIGN EXTRA INDENT).") + "Possible comment styles of the form (STYLE . (MULTI ALIGN EXTRA INDENT)). +STYLE should be a mnemonic symbol. +MULTI specifies that comments are allowed to span multiple lines. +ALIGN specifies that the `comment-end' markers should be aligned. +EXTRA specifies that an extra line should be used before and after the + region to comment (to put the `comment-end' and `comment-start'). +INDENT specifies that the `comment-start' markers should not be put at the + left margin but at the current indentation of the region to comment.") + +(defcustom comment-style 'plain + "*Style to be used for `comment-region'. +See `comment-styles' for a list of available styles." + :group 'comment + :type `(choice ,@(mapcar (lambda (s) `(const ,(car s))) comment-styles))) (defcustom comment-padding 1 - "Number of spaces `comment-region' puts between comment chars and text. -Can also be a string instead. + "Padding string that `comment-region' puts between comment chars and text. +Can also be an integer which will be automatically turned into a string +of the corresponding number of spaces. Extra spacing between the comment characters and the comment text makes the comment easier to read. Default is 1. nil means 0.") @@ -167,13 +164,15 @@ This is obsolete because you might as well use \\[newline-and-indent]." ;;;; Helpers ;;;; -(defun comment-string-strip (str before after) - (string-match (concat "\\`" (if before "\\s-*") - "\\(.*?\\)" (if after "\\s-*") +(defun comment-string-strip (str beforep afterp) + "Strip STR of any leading (if BEFOREP) and/or trailing (if AFTERP) space." + (string-match (concat "\\`" (if beforep "\\s-*") + "\\(.*?\\)" (if afterp "\\s-*") "\\'") str) (match-string 1 str)) (defun comment-string-reverse (s) + "Return the mirror image of string S, without any trailing space." (comment-string-strip (concat (nreverse (string-to-list s))) nil t)) (defun comment-normalize-vars (&optional noerror) @@ -198,12 +197,9 @@ This is obsolete because you might as well use \\[newline-and-indent]." ;;(setq comment-start (comment-string-strip comment-start t nil)) ;;(setq comment-end (comment-string-strip comment-end nil t)) ;; comment-continue - (unless (or (car comment-continue) (string= comment-end "")) + (unless (or comment-continue (string= comment-end "")) (set (make-local-variable 'comment-continue) - (cons (concat " " (substring comment-start 1)) - nil))) - (when (and (car comment-continue) (null (cdr comment-continue))) - (setcdr comment-continue (comment-string-reverse (car comment-continue)))) + (concat " " (substring comment-start 1)))) ;; comment-skip regexps (unless comment-start-skip (set (make-local-variable 'comment-start-skip) @@ -220,22 +216,38 @@ This is obsolete because you might as well use \\[newline-and-indent]." (regexp-quote (substring ce 1)) "\\)")))))) -(defmacro until (&rest body) - (let ((retsym (make-symbol "ret"))) - `(let (,retsym) - (while (not (setq ,retsym (progn ,@body)))) - ,retsym))) -(def-edebug-spec until t) +(defun comment-quote-re (str unp) + (concat (regexp-quote (substring str 0 1)) + "\\\\" (if unp "+" "*") + (regexp-quote (substring str 1)))) -(defun comment-end-quote-re (str &optional re) - "Make a regexp that matches the (potentially quoted) STR comment-end. -The regexp has one group in it which matches RE right after the -potential quoting." - (setq str (comment-string-strip str t t)) - (when (and comment-quote-nested (> (length str) 1)) - (concat (regexp-quote (substring str 0 1)) - "\\\\*\\(" re "\\)" - (regexp-quote (substring str 1))))) +(defun comment-quote-nested (cs ce unp) + "Quote or unquote nested comments. +If UNP is non-nil, unquote nested comment markers." + (setq cs (comment-string-strip cs t t)) + (setq ce (comment-string-strip ce t t)) + (when (and comment-quote-nested (> (length ce) 0)) + (let ((re (concat (comment-quote-re ce unp) + "\\|" (comment-quote-re cs unp)))) + (goto-char (point-min)) + (while (re-search-forward re nil t) + (goto-char (match-beginning 0)) + (forward-char 1) + (if unp (delete-char 1) (insert "\\")) + (when (= (length ce) 1) + ;; If the comment-end is a single char, adding a \ after that + ;; "first" char won't deactivate it, so we turn such a CE + ;; into !CS. I.e. for pascal, we turn } into !{ + (if (not unp) + (when (string= (match-string 0) ce) + (replace-match (concat "!" cs) t t)) + (when (and (< (point-min) (match-beginning 0)) + (string= (buffer-substring (1- (match-beginning 0)) + (1- (match-end 0))) + (concat "!" cs))) + (backward-char 2) + (delete-char (- (match-end 0) (match-beginning 0))) + (insert ce)))))))) ;;;; ;;;; Navigation @@ -335,9 +347,9 @@ If CONTINUE is non-nil, use the `comment-continuation' markers if any." (interactive "*") (let* ((empty (save-excursion (beginning-of-line) (looking-at "[ \t]*$"))) - (starter (or (and continue (car comment-continue)) + (starter (or (and continue comment-continue) (and empty block-comment-start) comment-start)) - (ender (or (and continue (car comment-continue) "") + (ender (or (and continue comment-continue "") (and empty block-comment-end) comment-end))) (cond ((null starter) @@ -413,86 +425,102 @@ With prefix ARG, kill comments on that many lines starting with this one." (defun comment-padright (str &optional n) "Construct a string composed of STR plus `comment-padding'. -It contains N copies of the last non-whitespace chars of STR. +It also adds N copies of the last non-whitespace chars of STR. If STR already contains padding, the corresponding amount is - ignored from `comment-padding'. -N defaults to 1. +ignored from `comment-padding'. +N defaults to 0. If N is `re', a regexp is returned instead, that would match - the string for any N." +the string for any N." (setq n (or n 0)) (when (and (stringp str) (not (string= "" str))) + ;; Separate the actual string from any leading/trailing padding (string-match "\\`\\s-*\\(.*?\\)\\s-*\\'" str) - (let ((s (match-string 1 str)) - (lpad (substring str 0 (match-beginning 1))) - (rpad (concat (substring str (match-end 1)) - (substring comment-padding + (let ((s (match-string 1 str)) ;actual string + (lpad (substring str 0 (match-beginning 1))) ;left padding + (rpad (concat (substring str (match-end 1)) ;original right padding + (substring comment-padding ;additional right padding (min (- (match-end 0) (match-end 1)) (length comment-padding)))))) - (if (symbolp n) - (concat (mapconcat (lambda (c) (concat (regexp-quote (string c)) "?")) - lpad "") - (regexp-quote s) "+" - (mapconcat (lambda (c) (concat (regexp-quote (string c)) "?")) - rpad "")) - (concat lpad s (make-string n (aref str (1- (match-end 1)))) rpad))))) + (if (not (symbolp n)) + (concat lpad s (make-string n (aref str (1- (match-end 1)))) rpad) + ;; construct a regexp that would match anything from just S + ;; to any possible output of this function for any N. + (concat (mapconcat (lambda (c) (concat (regexp-quote (string c)) "?")) + lpad "") ;padding is not required + (regexp-quote s) "+" ;the last char of S might be repeated + (mapconcat (lambda (c) (concat (regexp-quote (string c)) "?")) + rpad "")))))) ;padding is not required (defun comment-padleft (str &optional n) "Construct a string composed of `comment-padding' plus STR. -It contains N copies of the last non-whitespace chars of STR. +It also adds N copies of the first non-whitespace chars of STR. If STR already contains padding, the corresponding amount is - ignored from `comment-padding'. -N defaults to 1. +ignored from `comment-padding'. +N defaults to 0. If N is `re', a regexp is returned instead, that would match the string for any N." (setq n (or n 0)) (when (and (stringp str) (not (string= "" str))) + ;; Only separate the left pad because we assume there is no right pad. (string-match "\\`\\s-*" str) (let ((s (substring str (match-end 0))) (pad (concat (substring comment-padding (min (- (match-end 0) (match-beginning 0)) (length comment-padding))) (match-string 0 str))) - (c (aref str (match-end 0))) - (multi (or (not comment-quote-nested) (string= comment-end "") - (> (length str) (1+ (match-end 0)))))) - (if (symbolp n) - (concat "\\s-*" - (if multi (concat (regexp-quote (string c)) "*")) - (regexp-quote s)) - (concat pad (when multi (make-string n c)) s))))) + (c (aref str (match-end 0))) ;the first non-space char of STR + ;; We can only duplicate C if the comment-end has multiple chars + ;; or if comments can be nested, else the comment-end `}' would + ;; be turned into `}}}' where only the first ends the comment + ;; and the rest becomes bogus junk. + (multi (not (and comment-quote-nested + ;; comment-end is a single char + (string-match "\\`\\s-*\\S-\\s-*\\'" comment-end))))) + (if (not (symbolp n)) + (concat pad (when multi (make-string n c)) s) + ;; Construct a regexp that would match anything from just S + ;; to any possible output of this function for any N. + ;; We match any number of leading spaces because this regexp will + ;; be used for uncommenting where we might want to remove + ;; uncomment markers with arbitrary leading space (because + ;; they were aligned). + (concat "\\s-*" + (if multi (concat (regexp-quote (string c)) "*")) + (regexp-quote s)))))) (defun uncomment-region (beg end &optional arg) "Uncomment each line in the BEG..END region. -ARG is currently ignored." +The numeric prefix ARG can specify a number of chars to remove from the +comment markers." (interactive "*r\nP") (comment-normalize-vars) (if (> beg end) (let (mid) (setq mid beg beg end end mid))) (save-excursion (goto-char beg) - (unless (markerp end) (setq end (copy-marker end))) + (setq end (copy-marker end)) (let ((numarg (prefix-numeric-value arg)) spt) (while (and (< (point) end) (setq spt (comment-search-forward end t))) (let* ((ipt (point)) - ;; find the end of the comment + ;; Find the end of the comment. (ept (progn (goto-char spt) (unless (comment-forward) (error "Can't find the comment end")) - (point-marker))) - (block nil) - (end-quote-re (comment-end-quote-re comment-end "\\\\")) - (ccs (car comment-continue)) + (point))) + (box nil) + (ccs comment-continue) (srei (comment-padright ccs 're)) (sre (and srei (concat "^\\s-*?\\(" srei "\\)")))) (save-restriction (narrow-to-region spt ept) - ;; remove the comment-start + ;; Remove the comment-start. (goto-char ipt) (skip-syntax-backward " ") + ;; A box-comment starts with a looong comment-start marker. (when (> (- (point) (point-min) (length comment-start)) 7) - (setq block t)) + (setq box t)) (when (looking-at (regexp-quote comment-padding)) (goto-char (match-end 0))) (when (and sre (looking-at (concat "\\s-*\n\\s-*" srei))) @@ -501,35 +529,36 @@ ARG is currently ignored." (skip-syntax-backward " ") (delete-char (- numarg))) - ;; remove the end-comment (and leading padding and such) + ;; Remove the end-comment (and leading padding and such). (goto-char (point-max)) (comment-enter-backward) (unless (string-match "\\`\\(\n\\|\\s-\\)*\\'" - (buffer-substring (point) ept)) + (buffer-substring (point) (point-max))) (when (and (bolp) (not (bobp))) (backward-char)) - (if (null arg) (delete-region (point) ept) + (if (null arg) (delete-region (point) (point-max)) (skip-syntax-forward " ") (delete-char numarg))) - ;; unquote any nested end-comment - (when end-quote-re - (goto-char (point-min)) - (while (re-search-forward end-quote-re nil t) - (delete-region (match-beginning 1) (match-end 1)))) + ;; Unquote any nested end-comment. + (comment-quote-nested comment-start comment-end t) - ;; eliminate continuation markers as well - (let* ((cce (or (cdr comment-continue) - (comment-string-reverse comment-start))) - (erei (and block (comment-padleft cce 're))) - (ere (and erei (concat "\\(" erei "\\)\\s-*$"))) - (re (if (and sre ere) (concat sre "\\|" ere) (or sre ere)))) - (when re + ;; Eliminate continuation markers as well. + (when sre + (let* ((cce (comment-string-reverse (or comment-continue + comment-start))) + (erei (and box (comment-padleft cce 're))) + (ere (and erei (concat "\\(" erei "\\)\\s-*$")))) (goto-char (point-min)) - ;; there can't be a real SRE on the first line. - (when (and sre (looking-at sre)) (goto-char (match-end 0))) - (while (re-search-forward re nil t) + (while (progn + (if (and ere (re-search-forward + ere (line-end-position) t)) + (replace-match "" t t nil (if (match-end 2) 2 1)) + (setq ere nil)) + (forward-line 1) + (re-search-forward sre (line-end-position) t)) (replace-match "" t t nil (if (match-end 2) 2 1))))) - ;; go the the end for the next comment - (goto-char (point-max)))))))) + ;; Go the the end for the next comment. + (goto-char (point-max))))) + (set-marker end nil)))) (defun comment-make-extra-lines (cs ce ccs cce min-indent max-indent &optional block) (if block @@ -591,41 +620,54 @@ indentation to be kept as it was before narrowing." (defun comment-region-internal (beg end cs ce &optional ccs cce block lines indent) + "Comment region BEG..END. +CS and CE are the comment start resp. end string. +CCS and CCE are the comment continuation strings for the start resp. end +of lines (default to CS and CE). +BLOCK indicates that end of lines should be marked with either CCE, CE or CS +\(if CE is empty) and that those markers should be aligned. +LINES indicates that an extra lines will be used at the beginning and end +of the region for CE and CS. +INDENT indicates to put CS and CCS at the current indentation of the region +rather than at left margin." (assert (< beg end)) (let ((no-empty t)) - ;; sanitize ce and cce + ;; Sanitize CE and CCE. (if (and (stringp ce) (string= "" ce)) (setq ce nil)) (if (and (stringp cce) (string= "" cce)) (setq cce nil)) - ;; should we mark empty lines as well ? + ;; If CE is empty, multiline cannot be used. + (unless ce (setq ccs nil cce nil)) + ;; Should we mark empty lines as well ? (if (or ccs block lines) (setq no-empty nil)) - ;; make sure we have end-markers for BLOCK mode + ;; Make sure we have end-markers for BLOCK mode. (when block (unless ce (setq ce (comment-string-reverse cs)))) - ;; continuation defaults to the same - (if ccs (unless block (setq cce nil)) - (setq ccs cs cce ce)) + ;; If BLOCK is not requested, we don't need CCE. + (unless block (setq cce nil)) + ;; Continuation defaults to the same as CS and CE. + (unless ccs (setq ccs cs cce ce)) (save-excursion (goto-char end) + ;; If the end is not at the end of a line and the comment-end + ;; is implicit (i.e. a newline), explicitly insert a newline. (unless (or ce (eolp)) (insert "\n") (indent-according-to-mode)) (comment-with-narrowing beg end - (let ((ce-quote-re (comment-end-quote-re comment-end)) - (min-indent (point-max)) + (let ((min-indent (point-max)) (max-indent 0)) (goto-char (point-min)) - ;; loop over all lines to find the needed indentations - (until - (unless (looking-at "[ \t]*$") - (setq min-indent (min min-indent (current-indentation)))) - (when ce-quote-re - (let ((eol (line-end-position))) - (while (re-search-forward ce-quote-re eol 'move) - (incf eol) - (replace-match "\\" t t nil 1)))) - (end-of-line) - (setq max-indent (max max-indent (current-column))) - (or (eobp) (progn (forward-line) nil))) + ;; Quote any nested comment marker + (comment-quote-nested comment-start comment-end nil) - ;; inserting ccs can change max-indent by (1- tab-width) + ;; Loop over all lines to find the needed indentations. + (while + (progn + (unless (looking-at "[ \t]*$") + (setq min-indent (min min-indent (current-indentation)))) + (end-of-line) + (setq max-indent (max max-indent (current-column))) + (not (or (eobp) (progn (forward-line) nil))))) + + ;; Inserting ccs can change max-indent by (1- tab-width). (incf max-indent (+ (max (length cs) (length ccs)) -1 tab-width)) (unless indent (setq min-indent 0)) @@ -639,17 +681,18 @@ indentation to be kept as it was before narrowing." (goto-char (point-min)) ;; Loop over all lines from BEG to END. - (until - (unless (and no-empty (looking-at "[ \t]*$")) - (move-to-column min-indent t) - (insert cs) (setq cs ccs) - (end-of-line) - (if (eobp) (setq cce ce)) - (when cce - (when block (move-to-column max-indent t)) - (insert cce))) - (end-of-line) - (or (eobp) (progn (forward-line) nil)))))))) + (while + (progn + (unless (and no-empty (looking-at "[ \t]*$")) + (move-to-column min-indent t) + (insert cs) (setq cs ccs) ;switch to CCS after the first line + (end-of-line) + (if (eobp) (setq cce ce)) + (when cce + (when block (move-to-column max-indent t)) + (insert cce))) + (end-of-line) + (not (or (eobp) (progn (forward-line) nil)))))))))) (defun comment-region (beg end &optional arg) "Comment or uncomment each line in the region. @@ -709,8 +752,8 @@ The strings used as comment starts are built from (let ((s (comment-padleft comment-end numarg))) (and s (if (string-match comment-end-skip s) s (comment-padright comment-end)))) - (if multi (comment-padright (car comment-continue) numarg)) - (if multi (comment-padleft (cdr comment-continue) numarg)) + (if multi (comment-padright comment-continue numarg)) + (if multi (comment-padleft (comment-string-reverse comment-continue) numarg)) block lines (nth 3 style)))))) @@ -829,6 +872,28 @@ unless optional argument SOFT is non-nil." ;;; Change Log: ;; $Log: newcomment.el,v $ +;; Revision 1.7 2000/05/13 19:41:08 monnier +;; (comment-use-syntax): Change `maybe' to `undecided'. +;; (comment-quote-nested): New. Replaces comment-nested. +;; (comment-add): Turn into a mere defvar or a integer. +;; (comment-style): Change default to `plain'. +;; (comment-styles): Rename `plain' to `indent' and create a new plainer `plain'. +;; (comment-string-reverse): Use nreverse. +;; (comment-normalize-vars): Change `maybe' to `undecided', add comments. +;; Don't infer the setting of comment-nested anymore (the default for +;; comment-quote-nested is safe). Use comment-quote-nested. +;; (comment-end-quote-re): Use comment-quote-nested. +;; (comment-search-forward): Obey LIMIT. +;; (comment-indent): Don't skip forward further past comment-search-forward. +;; (comment-padleft): Use comment-quote-nested. +;; (comment-make-extra-lines): Use `cons' rather than `values'. +;; (comment-region-internal): New arg INDENT. Use line-end-position. +;; Avoid multiple-value-setq. +;; (comment-region): Follow the new comment-add semantics. +;; Don't do box comments any more. +;; (comment-box): New function. +;; (comment-dwim): Only do the region stuff is transient-mark-active. +;; ;; Revision 1.6 1999/12/08 00:19:51 monnier ;; various fixes and gratuitous movements. ;;