├── .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 | [![Melpa Status](http://melpa.org/packages/whitespace-cleanup-mode-badge.svg)](http://melpa.org/#/whitespace-cleanup-mode) 2 | [![Melpa Stable Status](http://stable.melpa.org/packages/whitespace-cleanup-mode-badge.svg)](http://stable.melpa.org/#/whitespace-cleanup-mode) 3 | [![Build Status](https://github.com/purcell/whitespace-cleanup-mode/actions/workflows/test.yml/badge.svg)](https://github.com/purcell/whitespace-cleanup-mode/actions/workflows/test.yml) 4 | Support me 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 | --------------------------------------------------------------------------------