Download
(defconst fillcode-version "0.7.1")
(require 'cl)
(require 'cc-bytecomp)
(cc-require 'cc-engine)
(define-minor-mode fillcode-mode
"Toggle fillcode mode.
With no argument, this command toggles the mode. Non-null prefix argument
turns on the mode. Null prefix argument turns off the mode.
Fillcode mode can intelligently fill some parts of source code, like function
calls and definitions, in many languages.
To see what version of fillcode you are running, enter `\\[fillcode-version]'.
For more information, see http://snarfed.org/space/fillcode"
nil
" Fillcode"
nil)
(defun fillcode-version ()
"Echo the current version of fillcode mode in the minibuffer."
(interactive)
(message "Using fillcode mode version %s" fillcode-version))
(defadvice fillcode-mode (after fillcode-mode-setup-and-teardown)
(make-local-variable
'fillcode-wrapped-fill-function)
(make-local-variable 'fill-paragraph-function)
(if fillcode-mode
(progn
(if (not (eq fill-paragraph-function 'fillcode-fill-paragraph))
(setq fillcode-wrapped-fill-function fill-paragraph-function)
(setq fillcode-wrapped-fill-function nil))
(setq fill-paragraph-function 'fillcode-fill-paragraph)
(ad-activate 'c-fill-paragraph))
(progn
(if (eq fill-paragraph-function 'fillcode-fill-paragraph)
(setq fill-paragraph-function fillcode-wrapped-fill-function))
(ad-deactivate 'c-fill-paragraph))))
(ad-activate 'fillcode-mode)
(defadvice c-fill-paragraph (around fillcode-if-in-code)
"If in fillcode-mode, fill code when in `cc-mode'.
`cc-mode' replaces `fill-paragraph' with its own function, `c-fill-paragraph',
which only calls fill-paragraph if it's inside a comment or string literal, and
narrows to that comment or string literal. Fillcode operates on code itself, so
it needs a chance to run (without narrowing!), which this advice provides."
(when fillcode-mode
(let ((fill-paragraph-function nil))
ad-do-it)
(fillcode-fill-paragraph arg)))
(defgroup fillcode nil
"Fill code"
:group 'fill)
(defcustom fillcode-fill-points
(list
";[^;]"
",[^,]"
"<<[^<]\\|>>[^>]"
"&&[^&]\\|||[^|]"
(concat "[<>!=]=[^=]\\|"
"\\s-<\\s-\\|"
"\\s->\\s-")
(concat "/[^=]\\|"
"\\s-\\*\\s-\\|"
"\\s--\\s-\\|"
"\\s-\\+[^+=]")
"[|~^][^&|=]"
"[([][^]})({[]"
"\\s-{[^({[]"
)
"A list of regular expressions used to find fill points.
A fill point is a point in an expression where the expression can be filled (ie
wrapped). This list contains regular expressions that identify fill points.
When fillcode runs, it uses this list, along with `fill-column', to determine
where to fill.
The list is ordered by precedence. The first regexp contains fill points that
fillcode prefers to fill at first, if possible. If none of them are found,
fillcode tries the next regexp, and so on.
Each regexp match must include one character *after* the fill point ends.
You may modify this to allow fillcode to handle new languages.
Note that the single = (assignment) operator and < and > operators are
unfortunately absent."
:type '(repeat string)
:group 'fillcode)
(defun build-re (re-list)
"Connect a list of regexps with |s to build a single regexp, and return it."
(mapconcat 'identity re-list "\\|"))
(defcustom fillcode-before-fill-point-re
(build-re
'("<<." ">>."))
"A regexp of fill points that should be filled *before*, not after.
By default, fillcode fills *after* fill points. When it fills at a fill point
that matches this regexp, it will fill *before* the fill point instead.
Each regexp match must include one character *after* the fill point ends.
See `fillcode-fill-points' for more."
:type '(repeat string)
:group 'fillcode)
(defcustom fillcode-start-token-re
(build-re
'("([^)]" "<<."))
"A list of strings to start filling at.
When fillcode is invoked, it first finds the beginning of the statement, then
looks for one of these strings. It will not fill anywhere before these strings.
This allows it to leave prefix clauses, like template declarations, intact.
Each regexp match must include one character *after* the fill point ends."
:type '(repeat string)
:group 'fillcode)
(defun fillcode-fill-paragraph (arg &optional arg2 arg3 arg4)
"Fill code at point if `fillcode-wrapped-fill-function' is nil.
If `fillcode-wrapped-fill-function' is nil, fills code. If it's
non-nil, runs it first, and only fills code if it returns nil.
Intended to be set as `fill-paragraph-function'."
(let ((ret
(cond
((eq fillcode-wrapped-fill-function 'c-fill-paragraph)
nil)
((and (eq major-mode 'python-mode)
(not (save-excursion (end-of-line) (fillcode-in-literal))))
nil)
(fillcode-wrapped-fill-function
(funcall fillcode-wrapped-fill-function arg)))))
(if (or ret (member (fillcode-in-literal) '(c c++ comment)))
ret
(save-excursion (save-restriction
(narrow-to-region (fillcode-beginning-of-statement)
(fillcode-end-of-statement))
(goto-char (point-min))
(let ((inserted-brace
(if (member major-mode
'(c-mode c++-mode java-mode objc-mode perl-mode))
(progn (insert " {\n") t)))
(inserted-opener
(progn
(re-search-forward "\\S-" nil t)
(when (eq ?\) (fillcode-syntax (char-before)))
(let ((char (cdr (aref c-mode-syntax-table (char-before)))))
(goto-char (point-at-bol))
(insert char "\n")
t)))))
(let ((start
(if (re-search-forward fillcode-start-token-re nil t)
(match-beginning 0)
(point))))
(goto-char start)
(save-excursion
(condition-case nil (forward-char) (end-of-buffer nil))
(fillcode-normalize-whitespace))
(condition-case nil
(while (< (point) (point-max))
(fillcode arg start)
(setq arg nil))
(end-of-buffer t)))
(goto-char (point-min))
(when inserted-brace
(delete-char 3))
(when inserted-opener
(delete-char 2))
t))))))
(defun fillcode (arg start)
"Fill code at point.
The actual function-call-filling algorithm. Fills function calls and prototypes
if it thinks the point is on a statement that has one. Uses start as a minimum
position bound; it won't fill before that position.
Returns t if it actually filled somewhere (not including just normalizing
whitespace), nil otherwise."
(let ((filled nil))
(catch 'sexp-end
(when arg
(when (re-search-forward fillcode-start-token-re nil t)
(fillcode-fill-at-match)
(setq filled t)))
(while (fillcode-forward)
(while (fillcode-in-literal)
(forward-char))
(when (fillcode-should-fill)
(fillcode-fill-at-fill-point 'backward start)
(setq filled t))
(when (and (eq ?\) (fillcode-syntax (char-after)))
(not (member (fillcode-syntax (char-before)) '(?\( ?\ ))))
(throw 'sexp-end t))
(when (eq ?\( (fillcode-syntax (char-after)))
(let ((next-fill-col (fillcode-fill-point-column-after-sexp)))
(when (and next-fill-col (> next-fill-col fill-column))
(let ((prev-fill-pt (fillcode-find-fill-point-backward
(max start (point-at-bol)))))
(when (not (eq ?\( (fillcode-syntax (char-before prev-fill-pt))))
(fillcode-fill-at-match)))))
(forward-char)
(if (fillcode nil start)
(fillcode-fill-at-fill-point 'forward)))))
filled))
(defun fillcode-fill-at-fill-point (direction &optional bound)
"Fill at the nearest fill point.
Nearest fill point is found either before or after point, depending on
whether direction is 'backward or 'forward, respectively. Will not fill at a
fill point past bound (a position)."
(let ((find-fn (if (eq direction 'forward)
'fillcode-find-fill-point-forward
'fillcode-find-fill-point-backward)))
(when (funcall find-fn bound)
(fillcode-fill-at-match))))
(defun fillcode-fill-here ()
"Fills right now, at point.
Inserts a newline and indents using `indent-according-to-mode'.
If filling brings the new line to the same point as it was on the previous
line, doesn't fill, leaves point where it was before, and returns nil."
(let ((orig-col (current-column)))
(insert "\n")
(indent-according-to-mode)
(when (>= (current-column) orig-col)
(indent-line-to (+ (fillcode-get-last-line-indent-offset)
(fillcode-get-mode-indent-offset))))
(if (>= (current-column) orig-col)
(progn
(delete-indentation)
t)
nil)))
(defun fillcode-forward ()
"Move forward to the next 'interesting' character. (Word-constituent
characters (letters, numbers, underscores, etc.) and whitespace are not
interesting.) Uses the current syntax table and `skip-syntax-forward'.
If point is already on an interesting character, more forward just one
character.
Return t if it moved point at all, nil otherwise."
(unless (eobp)
(if (eq (skip-syntax-forward "w_ >") 0)
(forward-char))
t))
(defun fillcode-forward-sexp ()
"Call forward-sexp and catch any errors.
Return t if it moved across an entire sexp, nil otherwise."
(unless (eobp)
(condition-case nil
(progn
(forward-sexp)
t)
(scan-error
(forward-char)
nil))))
(defun fillcode-beginning-of-statement ()
"Return the start position of the statement that point is currently in. Uses
the major mode's beginning-of-statement function, if it has one. Otherwise, for
safety, just uses the beginning of the line."
(case major-mode
((c-mode c++-mode java-mode objc-mode perl-mode)
(beginning-of-line)
(re-search-forward "\\S-\\S-" nil t)
(c-beginning-of-statement 1)
(point-at-bol))
((python-mode)
(save-excursion
(if (functionp 'py-goto-statement-at-or-above)
(py-goto-statement-at-or-above)
(python-beginning-of-statement))
(point)))
(otherwise
(point-at-bol))))
(defun fillcode-end-of-statement ()
"Return the end position of the statement that point is currently in.
Uses the major mode's end-of-statement function, if it has one. Otherwise,
for safety, just uses the end of the line."
(save-excursion
(if (equal ?{ (char-before))
(backward-char))
(case major-mode
((c-mode c++-mode java-mode objc-mode perl-mode)
(c-end-of-statement))
((python-mode)
(let ((start (point)))
(if (if (functionp 'py-goto-statement-below)
(py-goto-statement-below)
(python-next-statement))
(search-backward ")" start 'p)
(condition-case nil (forward-char) (error nil))))))
(point-at-eol)))
(defun fillcode-normalize-whitespace ()
"Normalize the current statement's whitespace, starting at point.
Specifically, no newlines, spaces before commas or open parens or after
close parens, one space after commas, one space before and after arithmetic
operators. Except string literals and comments, they're left untouched.
Uses `fillcode-collapse-whitespace-forward'."
(save-excursion
(forward-line)
(beginning-of-line)
(if (re-search-backward "\n\\s-*\n" nil t)
(narrow-to-region (match-end 0) (point-max))))
(save-excursion
(forward-line -1)
(end-of-line)
(if (re-search-forward "\n\\s-*\n" nil t)
(narrow-to-region (point-min) (match-beginning 0))))
(if (not (save-excursion (re-search-backward "\\S-" (point-at-bol) t)))
(if (re-search-forward "\\S-" (point-at-eol) t)
(backward-char)))
(while (not (eobp))
(fillcode-collapse-whitespace-forward)))
(defun fillcode-collapse-whitespace-forward ()
"Delete newlines, normalize whitespace, and/or move forward one character.
Specifically, no spaces before commas or open parens or after close parens,
one space after commas, one space before and after arithmetic operators.
Except string literals and comments, they're left untouched. Then advance
point to next non-whitespace char."
(let ((fill-point-re (build-re fillcode-fill-points)))
(cond
((fillcode-in-literal)
(when (save-excursion (backward-char) (not (fillcode-in-literal)))
(fixup-whitespace)
(forward-char))
(forward-char)
(if (equal "\n" (char-to-string (char-before)))
(indent-according-to-mode)))
((eolp)
(delete-indentation t))
((looking-at "\\s-")
(delete-horizontal-space)
(when (or (eq ?\( (fillcode-syntax (char-after)))
(not (looking-at fill-point-re)))
(fixup-whitespace)
(if (looking-at "\\s-")
(forward-char))))
((and (looking-at fill-point-re)
(not (looking-at "[,;([{]\\|&[^&]\\||[^| ]")))
(insert " ")
(goto-char (match-end 0)))
((and (save-excursion
(progn
(condition-case nil (forward-char) (error nil))
(re-search-backward fill-point-re (point-at-bol) t)))
(equal (point) (1- (match-end 0)))
(not (save-excursion (backward-char) (fillcode-in-literal))))
(fixup-whitespace)
(forward-char (if (looking-at " ") 2 1)))
(t (forward-char)))))
(defun fillcode-should-fill ()
"Return t if we should fill at the last fill point, nil otherwise.
We should fill if:
- there's a fill point on this line, AND
- we're not in a comment or string literal, AND
- the current char is at or beyond `fill-column'"
(and
(not (fillcode-in-literal))
(>= (current-column) fill-column)
(save-excursion
(fillcode-find-fill-point-backward))))
(defun fillcode-find-fill-point-forward (&optional bound)
(fillcode-find-fill-point-helper 're-search-forward
(if bound
(min bound (point-at-eol))
(point-at-eol))))
(defun fillcode-find-fill-point-backward (&optional bound)
(forward-char)
(fillcode-find-fill-point-helper 're-search-backward
(if bound
(max bound (point-at-bol))
(point-at-bol))))
(defun fillcode-fill-point-column-after-sexp ()
"Return the column of the closest fill point after the sexp at point.
If there's no full sexp after point, returns nil."
(save-excursion
(if (fillcode-forward-sexp)
(let ((fill-pt (fillcode-find-fill-point-forward)))
(goto-char (if fill-pt fill-pt (point-at-eol)))
(current-column))
nil)))
(defun fillcode-find-fill-point-helper (re-search-fn bound)
"If there's a valid fill point on the current line, returns its position.
Fill points are defined by `fillcode-fill-points'; commas, open parens,
arithmetic operators, ||s, &&s, etc. This function finds the closest one either
before or after point, depending on `forward'.
It searches for fill points in the order that their regexps are specified in
`fillcode-fill-points'.
If no fill point is found, returns nil."
(catch 'found
(dolist (re fillcode-fill-points)
(save-excursion
(while (funcall re-search-fn re bound t)
(save-match-data
(when (save-match-data (string-match fillcode-before-fill-point-re
(match-string 0)))
(goto-char (match-beginning 0)))
(when (not (or
(fillcode-in-literal)
(save-excursion (skip-chars-backward " \t")
(eq (point) (point-at-bol)))))
(throw 'found (point)))))))
nil))
(defun fillcode-fill-at-match ()
"Fill at the fill point currently stored in the match data.
This function expects a fill point to be stored in the match data. It should
usually be called after one of the `fillcode-find-fill-point-*' functions. It
leaves point at the first non-whitespace character on the new line."
(save-excursion
(goto-char
(if (save-match-data
(string-match fillcode-before-fill-point-re (match-string 0)))
(match-beginning 0)
(1- (match-end 0))))
(skip-chars-backward " \t")
(fillcode-fill-here)))
(defun fillcode-in-literal ()
"Return non-nil if inside a comment or string literal, nil otherwise.
Determines whether point is inside a comment, string literal, or other segment
that shouldn't be normalized or filled. Piggybacks on the major modes, since
it will usually have its code for this.
Unfortunately, the major modes' in-literal functions (e.g. `c-in-literal' do
*not* consider literals' start tokens (\", ', /*, //, #) to be part of the
literal, so they return nil if point is on the start token. We want them to
return non-nil if we're past the first char of the start token, so
`fillcode-in-literal' returns non-nil instead."
(let ((in-literal-fn
(case major-mode
((python-mode) (if (functionp 'py-in-literal)
'py-in-literal 'python-in-string/comment))
(otherwise 'c-in-literal))))
(or (funcall in-literal-fn)
(condition-case nil
(save-excursion
(forward-char)
(or (funcall in-literal-fn)
(progn
(forward-char)
(member (funcall in-literal-fn) '(c c++)))))
(error nil)))))
(defun fillcode-get-mode-indent-offset ()
"Returns the indent offset, ie the number of columns to indent, in the
current mode."
(case major-mode
((python-mode) py-indent-offset)
(otherwise c-basic-offset)))
(defun fillcode-get-last-line-indent-offset ()
"Returns the indent offset, ie the column of the first non-whitespace
character, of the current line."
(save-excursion
(forward-line -1)
(beginning-of-line)
(skip-chars-forward " \t")
(current-column)))
(defun fillcode-syntax (char)
"Returns the argument's syntax class, or nil if the argument is nil."
(if char
(char-syntax char)))
(provide 'fillcode)