Subject: Re: bug#2887: Suggestions for simple.el

From: "Arni Magnusson" <[email-masked]>
Date: Tue, 7 Apr 2009 02:46:50 -0000 (GMT)

>> My commands that call pos-at-*-of-line work faster than I can blink
>> my eyes, but I understand your concern.
>
> Try them on 200MB buffers.

When I'm editing a 212MB text file, many things become sluggish, but
pos-at-*-of-line is not one of them:

  M-x benchmark (pos-at-end-of-line 1000000)    ; 0.157s
  10 M-x benchmark (pos-at-end-of-line 1000000) ; 0.719s

This speed is similar to line-*-position:

  M-x benchmark (line-end-position 1000000)    ; 0.172s
  10 M-x benchmark (line-end-position 1000000) ; 0.734s

>> Yes, it would be a nice improvement to upgrade the
>> `delete-trailing-whitespace' command so it counts cleaned lines.
>
> Feel free to provide a patch for it (tho, since we're in the pretest,
> there's no hurry: it won't be installed right now).

Here is a candidate upgrade of `delete-trailing-whitespace':

(defun delete-trailing-whitespace ()
  "Delete all the trailing whitespace across the current buffer.
All whitespace after the last non-whitespace character in a line is deleted.
This respects narrowing, created by \\[narrow-to-region] and friends.
A formfeed is not considered whitespace by this function."
  (interactive "*")
  (save-match-data
    (save-excursion
      (goto-char (point-min))
      (let ((count 0)(table (syntax-table)))
        (modify-syntax-entry ?\f "." table) ; never delete formfeeds
        (modify-syntax-entry ?\n "." table) ; never delete newline
        (with-syntax-table table
          (while (re-search-forward "\\s-+$" nil t)
            (replace-match "")(setq count (1+ count)))
          (message "Cleaned %d lines" count))))))

Although the code is substantially different, it performs precisely the
same deletions as the existing revision 1.985, and the computational speed
is comparable. The user-visible difference is that it reports how many
lines were cleaned. The code is clear and extensible.

One possible change is to add one important line, while still fulfilling
the current documentation:

  (modify-syntax-entry ?\r " " table) ; always delete trailing ^M

And here's a version that deletes only trailing spaces, tabs, and ^M glyphs:

(defun delete-trailing-whitespace ()
  "Delete all the trailing whitespace across the current buffer.
All whitespace after the last non-whitespace character in a line is deleted.
This respects narrowing, created by \\[narrow-to-region] and friends.
A formfeed is not considered whitespace by this function."
  (interactive "*")
  (save-match-data
    (save-excursion
      (goto-char (point-min))
      (let ((count 0))
        (while (re-search-forward "[ \r\t]+$" nil t)
          (replace-match "")(setq count (1+ count)))
        (message "Cleaned %d lines" count)))))

This performs 4x faster than the first candidate upgrade, because the
regexp matches only 3 characters, whereas "\\s-+$" matches 147 different
characters in text-mode. The syntax table definitions of whitespace can be
confusing, e.g. the ^M glyph is considered whitespace in text-mode but not
in emacs-lisp-mode...

After this explanatory introduction, my real proposal is to define a
variable to determine the behavior of `delete-trailing-whitespace':

(defvar trailing-whitespace-regexp "\\s-+$"
  "Regular expression describing what `delete-trailing-whitespace'
should delete. Note that regardless of the value of
`trailing-whitespace-regexp', `delete-trailing-whitespace' will never
delete formfeed and newline characters.

The default value \"\\\\s-+$\" uses the current syntax table definitions
of whitespace, but an expression like \"[ \\r\\t]+$\" is faster and
consistent across modes.")

(defun delete-trailing-whitespace ()
  "Delete all trailing whitespace, as defined by
`trailing-whitespace-regexp', in the current buffer. All whitespace
after the last non-whitespace character in a line is deleted. This
respects narrowing, created by \\[narrow-to-region] and friends.

Note that regardless of the value of `trailing-whitespace-regexp',
`delete-trailing-whitespace' will never delete formfeed and newline
characters."
  (interactive "*")
  (save-match-data
    (save-excursion
      (goto-char (point-min))
      (let ((count 0)(table (syntax-table)))
        (modify-syntax-entry ?\f "." table) ; never delete formfeeds
        (modify-syntax-entry ?\n "." table) ; never delete newline
        (with-syntax-table table
          (while (re-search-forward whitespace-regexp nil t)
            (replace-match "")(setq count (1+ count)))
          (message "Cleaned %d lines" count))))))

This version of `delete-trailing-whitespace' can mimic my `clean-trails',
making that function unnecessary.

>> These functions are used in a wide variety of situations (text, code,
>> data), making guesswork practically hopeless. I use
>> `delete-indentation' and `delete-indentation-nospace' a lot, and I'm
>> not sure which one I use more frequently.
>
> What bindings do you use?

M-j and C-M-j, overriding the defaults.

>> Allow me to propose the following bindings that are undefined in
>> Emacs 22.3.1:
>
> There's a good reason why they're undefined: most of those keys can't
> be pressed under a tty.

Good point. Here are some undefined keystrokes in Emacs 22.3.1 that seem
to get through:

  C-x j    backward-delete-word
  C-x C-j  delete-word
  C-x x    kill-line-or-region
  M-n      pull-line-down
  M-p      pull-line-up
  C-M-z    zap-back-to-char
  C-M-m    zap-up-to-char
  C-x C-a  delete-all-blank-lines
  M-&      delete-indentation-nospace
  C-x w    goto-longest-line
  C-x y    downcase-word-or-region
  C-x C-y  upcase-word-or-region

Thank you for your patience and thoughtful responses,

Arni