Comments

Bernhard Herzog bh at intevation.de
Thu May 9 16:44:21 EDT 2002


Philip Swartzleonard <starx at pacbell.net> writes:

[Emacs python-mode problems with fill-paragraph (M-Q) and comments]

I ran into this as well. I couldn't find a simple solution, so I hacked
up complex perhaps not very elegant one. Starting out with the fill
function from the lisp-mode. Over time I extended it a bit and now it
handles strings as well.

Basically, it's a python-mode specific fill-paragraph-function, so put
something like (setq fill-paragraph-function 'python-fill-paragraph-new)
into your python-mode hook.

  Bernhard


Here's the code.

(defun python-fill-comment (&optional justify)
  "Fill the comment paragraph around point"
  (let (;; Non-nil if the current line contains a comment.
	has-comment
	
	;; If has-comment, the appropriate fill-prefix for the comment.
	comment-fill-prefix)
    
    ;; Figure out what kind of comment we are looking at.
    (save-excursion
      (beginning-of-line)
      (cond

       ;; A line with nothing but a comment on it?
       ((looking-at "[ \t]*#[# \t]*")
	(setq has-comment t
	      comment-fill-prefix (buffer-substring (match-beginning 0)
						    (match-end 0))))

       ;; A line with some code, followed by a comment? Remember that the hash
       ;; which starts the comment shouldn't be part of a string or character.
       ((progn
	  (while (not (looking-at "#\\|$"))
	    (skip-chars-forward "^#\n\"'\\")
	    (cond
	     ((eq (char-after (point)) ?\\) (forward-char 2))
	     ((memq (char-after (point)) '(?\" ?')) (forward-sexp 1))))
	  (looking-at "#+[\t ]*"))
	(setq has-comment t)
	(setq comment-fill-prefix
	      (concat (make-string (current-column) ? )
		      (buffer-substring (match-beginning 0) (match-end 0)))))))

    (if (not has-comment)
	(fill-paragraph justify)

      ;; Narrow to include only the comment, and then fill the region.
      (save-restriction
	(narrow-to-region
	 ;; Find the first line we should include in the region to fill.
	 (save-excursion
	   (while (and (zerop (forward-line -1))
		       (looking-at "^[ \t]*#")))
	   ;; We may have gone to far.  Go forward again.
	   (or (looking-at "^[ \t]*#")
	       (forward-line 1))
	   (point))
	 ;; Find the beginning of the first line past the region to fill.
	 (save-excursion
	   (while (progn (forward-line 1)
			 (looking-at "^[ \t]*#")))
	   (point)))

	;; Lines with only hashes on them can be paragraph boundaries.
	(let ((paragraph-start (concat paragraph-start "\\|[ \t#]*$"))
	      (paragraph-separate (concat paragraph-separate "\\|[ \t#]*$"))
	      (fill-prefix comment-fill-prefix))
	  ;;(message "paragraph-start %S paragraph-separate %S"
	  ;;paragraph-start paragraph-separate)
	  (fill-paragraph justify))))
    t))
  


(defun python-fill-string (start &optional justify)
  "Fill the paragraph around (point) in the string starting at start"
  ;; basic strategy: narrow to the string and call the default
  ;; implementation
  (let (;; the start of the string's contents
	string-start
	;; the end of the string's contents
	string-end
	;; length of the string's delimiter
	delim-length
	;; The string delimiter
	delim
	)
  (save-excursion
    (goto-char start)
    (if (looking-at "\\('''\\|\"\"\"\\|'\\|\"\\)\\\\?\n?")
	(setq string-start (match-end 0)
	      delim-length (- (match-end 1) (match-beginning 1))
	      delim (buffer-substring-no-properties (match-beginning 1)
						    (match-end 1)))
      (error "The parameter start is not the beginning of a python string"))

    ;; if the string is the first token on a line and doesn't start with
    ;; a newline, fill as if the string starts at the beginning of the
    ;; line. this helps with one line docstrings
    (save-excursion
      (beginning-of-line)
      (and (/= (char-before string-start) ?\n)
	   (looking-at (concat "[ \t]*" delim))
	   (setq string-start (point))))
    
    (forward-sexp (if (= delim-length 3) 2 1))
    
    ;; with both triple quoted strings and single/double quoted strings
    ;; we're now directly behind the first char of the end delimiter
    ;; (this doesn't work correctly when the triple quoted string
    ;; contains the quote mark itself). The end of the string's contents
    ;; is one less than point
    (setq string-end (1- (point))))

  ;; Narrow to the string's contents and fill the current paragraph
  (save-restriction
    (narrow-to-region string-start string-end)
    (let ((ends-with-newline (= (char-before (point-max)) ?\n)))
      (fill-paragraph justify)
      (if (and (not ends-with-newline)
	       (= (char-before (point-max)) ?\n))
	  ;; the default fill-paragraph implementation has inserted a
	  ;; newline at the end. Remove it again.
	  (save-excursion
	    (goto-char (point-max))
	    (delete-char -1)))))

  ;; return t to indicate that we've done our work
  t))

    
(defun python-fill-paragraph-new (&optional justify)
  "Like \\[fill-paragraph], but handle Python comments and strings.
If any of the current line is a comment, fill the comment or the
paragraph of it that point is in, preserving the comment's indentation
and initial `#'s.
If point is inside a string, narrow to that string and fill.
"
  (interactive "P")
  (let* ((bod (py-point 'bod))
	 (pps (parse-partial-sexp bod (point))))
    (cond
     ;; are we inside a comment or on a line with only whitespace before
     ;; the comment start?
     ((or (nth 4 pps)
	  (save-excursion (beginning-of-line) (looking-at "[ \t]*#")))
      (python-fill-comment justify))
     ;; are we inside a string?
     ((nth 3 pps)
      (python-fill-string (nth 2 pps)))
     ;; otherwise use the default
     (t
      (fill-paragraph justify)))))


-- 
Intevation GmbH                                 http://intevation.de/
Sketch                                 http://sketch.sourceforge.net/
MapIt!                                           http://www.mapit.de/



More information about the Python-list mailing list