├── .github
├── FUNDING.yml
└── workflows
│ └── test.yml
├── Makefile
├── README.md
└── whitespace-cleanup-mode.el
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: sanityinc
2 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | push:
6 | paths-ignore:
7 | - '**.md'
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | strategy:
13 | matrix:
14 | emacs_version:
15 | - 24.1
16 | - 24.5
17 | - 25.1
18 | - 25.3
19 | - 26.1
20 | - 26.3
21 | - 27.1
22 | - 27.2
23 | - 28.1
24 | - 28.2
25 | - snapshot
26 | steps:
27 | - uses: purcell/setup-emacs@master
28 | with:
29 | version: ${{ matrix.emacs_version }}
30 |
31 | - uses: actions/checkout@v2
32 | - name: Run tests
33 | run: make
34 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | EMACS ?= emacs
2 |
3 | # A space-separated list of required package names
4 | NEEDED_PACKAGES = package-lint
5 |
6 | INIT_PACKAGES="(progn \
7 | (require 'package) \
8 | (push '(\"melpa\" . \"https://melpa.org/packages/\") package-archives) \
9 | (package-initialize) \
10 | (dolist (pkg '(${NEEDED_PACKAGES})) \
11 | (unless (package-installed-p pkg) \
12 | (unless (assoc pkg package-archive-contents) \
13 | (package-refresh-contents)) \
14 | (package-install pkg))) \
15 | )"
16 |
17 | all: compile package-lint clean-elc
18 |
19 | package-lint:
20 | ${EMACS} -Q --eval ${INIT_PACKAGES} -batch -f package-lint-batch-and-exit whitespace-cleanup-mode.el
21 |
22 | compile: clean-elc
23 | ${EMACS} -Q --eval ${INIT_PACKAGES} -L . -batch -f batch-byte-compile *.el
24 |
25 | clean-elc:
26 | rm -f f.elc
27 |
28 | .PHONY: all compile clean-elc package-lint
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](http://melpa.org/#/whitespace-cleanup-mode)
2 | [](http://stable.melpa.org/#/whitespace-cleanup-mode)
3 | [](https://github.com/purcell/whitespace-cleanup-mode/actions/workflows/test.yml)
4 |
5 |
6 | whitespace-cleanup-mode.el
7 | ==========================
8 |
9 | This Emacs library minor mode will intelligently call `whitespace-cleanup`
10 | before buffers are saved.
11 |
12 | `whitespace-cleanup` is a handy function, but putting it in
13 | `before-save-hook` for every buffer is overkill, and causes messy diffs
14 | when editing third-party code that did not initially have clean whitespace.
15 |
16 | Additionally, whitespace preferences are often project-specific, and
17 | it's inconvenient to set up `before-save-hook` in a `.dir-locals.el` file.
18 |
19 | `whitespace-cleanup-mode` is a minor mode which calls `whitespace-cleanup`
20 | before saving the current buffer, by default only if the whitespace in the buffer
21 | was initially clean. It determines this by quickly checking to see if
22 | `whitespace-cleanup` would have any effect on the buffer. With the custom variable
23 | `whitespace-cleanup-mode-only-if-initially-clean` toggled off, it will always
24 | clean up the buffer for you.
25 |
26 |
27 | Installation
28 | =============
29 |
30 | If you choose not to use one of the convenient
31 | packages in [Melpa][melpa], you'll need to
32 | add the directory containing `whitespace-cleanup-mode.el` to your `load-path`, and
33 | then `(require 'whitespace-cleanup-mode)`.
34 |
35 | Usage
36 | =====
37 |
38 | Enable `whitespace-cleanup-mode` in an individual buffer like this:
39 |
40 | ```
41 | M-x whitespace-cleanup-mode
42 | ```
43 |
44 | Optionally enable it everywhere by default using
45 | `global-whitespace-cleanup-mode`. (You can override that by setting
46 | `whitespace-cleanup-mode` to `nil` in file or directory local
47 | variables.)
48 |
49 | Alternatively, enable whitespace cleanup for a particular major mode:
50 |
51 | ```elisp
52 | (add-hook 'ruby-mode-hook 'whitespace-cleanup-mode)
53 | ```
54 |
55 | To enable it for an entire project, set `whitespace-cleanup-mode` to `t` in
56 | your `.dir-locals.el` file.
57 |
58 | This mode is built upon some functionality built into `whitespace-mode`, namely
59 | `whitespace-action`: if you would rather see a warning when saving a file with
60 | bogus whitespace, or even have the save aborted, then set that variable.
61 |
62 | [melpa]: http://melpa.org
63 |
64 |
65 |
66 | [💝 Support this project and my other Open Source work via Patreon](https://www.patreon.com/sanityinc)
67 |
68 | [💼 LinkedIn profile](https://uk.linkedin.com/in/stevepurcell)
69 |
70 | [✍ sanityinc.com](http://www.sanityinc.com/)
71 |
--------------------------------------------------------------------------------
/whitespace-cleanup-mode.el:
--------------------------------------------------------------------------------
1 | ;;; whitespace-cleanup-mode.el --- Intelligently call whitespace-cleanup on save -*- lexical-binding: t -*-
2 |
3 | ;; Copyright (C) 2013-2020 Steve Purcell
4 |
5 | ;; Author: Steve Purcell
6 | ;; Package-Version: 0
7 | ;; Package-Requires: ((emacs "24.1"))
8 | ;; URL: https://github.com/purcell/whitespace-cleanup-mode
9 | ;; Keywords: convenience
10 |
11 | ;; This program is free software; you can redistribute it and/or modify
12 | ;; it under the terms of the GNU General Public License as published by
13 | ;; the Free Software Foundation, either version 3 of the License, or
14 | ;; (at your option) any later version.
15 |
16 | ;; This program is distributed in the hope that it will be useful,
17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 | ;; GNU General Public License for more details.
20 |
21 | ;; You should have received a copy of the GNU General Public License
22 | ;; along with this program. If not, see .
23 |
24 | ;;; Commentary:
25 |
26 | ;; `whitespace-cleanup' is a handy function, but putting it in
27 | ;; `before-save-hook' for every buffer is overkill, and causes messy diffs
28 | ;; when editing code that did not initially have clean whitespace.
29 |
30 | ;; Additionally, whitespace preferences are often project-specific, and
31 | ;; it's inconvenient to set up `before-save-hook' in a ".dir-locals.el" file.
32 |
33 | ;; `whitespace-cleanup-mode' is a minor mode which calls `whitespace-cleanup'
34 | ;; before saving the current buffer, but only if the whitespace in the buffer
35 | ;; was initially clean.
36 |
37 | ;; Set `whitespace-cleanup-mode' to t in ".dir-locals.el" to enable the mode
38 | ;; project-wide, or add it to the hook for the major mode(s) of your choice.
39 |
40 | ;; To clean up whitespace everywhere by default, enable
41 | ;; `global-whitespace-cleanup-mode'.
42 |
43 | ;; To clean up whitespace at save even if it was intitially dirty,
44 | ;; unset `whitespace-cleanup-mode-only-if-initially-clean'.
45 |
46 | ;; This mode is built upon some functionality provided by `whitespace-mode', namely
47 | ;; `whitespace-action': if you would rather see a warning when saving a file with
48 | ;; bogus whitespace, or even have the save aborted, then set that variable.
49 |
50 | ;;; Code:
51 |
52 | (require 'whitespace)
53 |
54 | ;;;###autoload
55 | (defgroup whitespace-cleanup nil
56 | "Automatically clean up whitespace on save."
57 | :group 'convenience)
58 |
59 | (defcustom whitespace-cleanup-mode-preserve-point nil
60 | "When non-nil, the column position of point will be restored after cleanup.
61 | When the point is positioned after trailing indentation at
62 | save-time, the normal behaviour is that the point will jump back
63 | to the end of the line's trimmed content. When this variable is
64 | non-nil, then the trimmed space is re-added after save, but
65 | without marking the buffer as modified. This allows
66 | uninterrupted editing with very short autosave intervals."
67 | :group 'whitespace-cleanup
68 | :type 'boolean)
69 |
70 | (defcustom whitespace-cleanup-mode-only-if-initially-clean t
71 | "When non-nil, only clean up whitespace at save if it was clean initially.
72 | The check for initial cleanliness is done when `whitespace-cleanup-mode' is
73 | enabled."
74 | :group 'whitespace-cleanup
75 | :type 'boolean)
76 |
77 | (defcustom whitespace-cleanup-mode-ignore-modes
78 | '(special-mode view-mode comint-mode cider-repl-mode haskell-interactive-mode)
79 | "List of major modes in which cleanup will not be automatically enabled.
80 | If the major mode of a buffer is derived from one of these, then
81 | `global-whitespace-cleanup-mode' will not enable `whitespace-cleanup-mode'
82 | in that buffer."
83 | :type '(repeat symbol)
84 | :group 'whitespace-cleanup)
85 |
86 | (defvar whitespace-cleanup-mode-initially-clean nil
87 | "Records whether `whitespace-cleanup' was a no-op when the mode launched.")
88 | (make-variable-buffer-local 'whitespace-cleanup-mode-initially-clean)
89 |
90 | (defun whitespace-cleanup-mode-buffer-is-clean-p ()
91 | "Return t iff the whitespace in the current buffer is clean."
92 | (let ((contents (buffer-substring-no-properties (point-min) (point-max)))
93 | (orig-indent-tabs-mode indent-tabs-mode)
94 | (orig-whitespace-style whitespace-style))
95 | (with-temp-buffer
96 | (insert contents)
97 | (set-buffer-modified-p nil)
98 | (set (make-local-variable 'indent-tabs-mode) orig-indent-tabs-mode)
99 | (set (make-local-variable 'whitespace-style) orig-whitespace-style)
100 | (whitespace-cleanup)
101 | (not (buffer-modified-p)))))
102 |
103 | (defun whitespace-cleanup-mode-mode-line ()
104 | "Return a string for mode-line.
105 | Use '!' to signify that the buffer was not initially clean."
106 | (concat " WSC"
107 | (unless whitespace-cleanup-mode-initially-clean
108 | "!")))
109 |
110 | ;;;###autoload
111 | (define-minor-mode whitespace-cleanup-mode
112 | "Automatically call `whitespace-cleanup' on save."
113 | :lighter (:eval (whitespace-cleanup-mode-mode-line))
114 | (if whitespace-cleanup-mode
115 | (progn
116 | (setq whitespace-cleanup-mode-initially-clean
117 | (whitespace-cleanup-mode-buffer-is-clean-p))
118 | (add-hook 'write-file-functions 'whitespace-cleanup-mode-write-file nil t))
119 | (remove-hook 'write-file-functions 'whitespace-cleanup-mode-write-file t)))
120 |
121 | ;;;###autoload
122 | (put 'whitespace-cleanup-mode 'safe-local-variable 'booleanp)
123 |
124 | ;;;###autoload
125 | (define-globalized-minor-mode global-whitespace-cleanup-mode
126 | whitespace-cleanup-mode
127 | whitespace-cleanup-mode--maybe)
128 |
129 | (defun whitespace-cleanup-mode--maybe ()
130 | "Enable `whitespace-cleanup-mode' if appropriate in this buffer."
131 | (unless (or (minibufferp)
132 | (apply 'derived-mode-p whitespace-cleanup-mode-ignore-modes))
133 | (whitespace-cleanup-mode 1)))
134 |
135 | (defun whitespace-cleanup-mode-write-file ()
136 | "Function added to `write-file-functions'."
137 | (when (and whitespace-cleanup-mode
138 | (not buffer-read-only)
139 | (or (not whitespace-cleanup-mode-only-if-initially-clean)
140 | whitespace-cleanup-mode-initially-clean))
141 | (let ((whitespace-action (or whitespace-action '(auto-cleanup)))
142 | (col (current-column)))
143 | (whitespace-write-file-hook)
144 | (when whitespace-cleanup-mode-preserve-point
145 | (move-to-column col t)
146 | (set-buffer-modified-p nil)))))
147 |
148 | (defadvice whitespace-cleanup-region (around whitespace-cleanup-mode-mark-clean (start end) activate)
149 | "When `whitespace-cleanup' is called, mark the buffer as initially clean."
150 | (let ((cleaning-up-whole-buffer (and (eq start (point-min))
151 | (eq end (point-max)))))
152 | ad-do-it
153 | (when cleaning-up-whole-buffer
154 | (setq whitespace-cleanup-mode-initially-clean t))))
155 |
156 | (provide 'whitespace-cleanup-mode)
157 | ;;; whitespace-cleanup-mode.el ends here
158 |
--------------------------------------------------------------------------------