From 1b76276ea831ee5cb657e0261ff5fe472626118f Mon Sep 17 00:00:00 2001 From: Tom Ryder Date: Fri, 20 Jul 2018 20:31:13 +1200 Subject: Overhaul for new version Refactor completely, mostly for clarity of code rather than speed. Also accept a range and deal with vertical whitespace trimming intelligently based on its value. --- README.md | 7 +- doc/strip_trailing_whitespace.txt | 27 +++---- plugin/strip_trailing_whitespace.vim | 152 +++++++++++++++++++++-------------- 3 files changed, 106 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 4ea75b4..abddc14 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ strip\_trailing\_whitespace.vim =============================== -This plugin provides a mapping target with the author's approach to stripping +This plugin provides a user command with the author's approach to stripping trailing whitespace from an entire buffer, including removing empty or whitespace-only lines at the end of the buffer, without making command noise and without moving the cursor from its current position. @@ -9,15 +9,14 @@ and without moving the cursor from its current position. This is a very commonly written and implemented plugin, but I wrote my own because I could not find a plugin that did this in exactly the way I wanted: -- Provide a `` mapping +- Accept an optional range - Strip trailing lines as well as trailing spaces -- Accurately report what was changed +- Report what was changed, accurately - Work with even very old Vim (>=6.0) - Work with a single `undo` - Don't move the cursor - Don't change the search pattern - Don't define an `autocmd` -- Don't define a user command - Don't force a key mapping - Don't define a global function diff --git a/doc/strip_trailing_whitespace.txt b/doc/strip_trailing_whitespace.txt index 8ee64f3..c29b664 100644 --- a/doc/strip_trailing_whitespace.txt +++ b/doc/strip_trailing_whitespace.txt @@ -1,8 +1,8 @@ -*strip_trailing_whitespace.txt* For Vim version 6.0 Last change: 2018 June 27 +*strip_trailing_whitespace.txt* For Vim version 6.0 Last change: 2018 Jul 20 DESCRIPTION *strip_trailing_whitespace* -This plugin provides a mapping target with the author's approach to stripping +This plugin provides a user command with the author's approach to stripping trailing whitespace from an entire buffer, including removing empty or whitespace-only lines at the end of the buffer, without making command noise and without moving the cursor from its current position. @@ -10,31 +10,30 @@ and without moving the cursor from its current position. This is a very commonly written and implemented plugin, but I wrote my own because I could not find a plugin that did this in exactly the way I wanted: -- Provide a || mapping +- Accept an optional range - Strip trailing lines as well as trailing spaces -- Accurately report what was changed +- Report what was changed, accurately - Work with even very old Vim (>=6.0) - Work with a single |undo| - Don't move the cursor - Don't change the search pattern - Don't define an |autocmd| -- Don't define a user command - Don't force a key mapping - Don't define a global function REQUIREMENTS *strip_trailing_whitespace-requirements* -This plugin is only available if 'compatible' is not set. +This plugin is only available if 'compatible' is not set. It also requires the +|+user_commands| feature. -MAPPINGS *strip_trailing_whitespace-mappings* +COMMANDS *strip_trailing_whitespace-commands* + + *:StripTrailingWhitespace* +Strip trailing space appropriately for the range of lines selected, defaulting +to the entire buffer. Remove all horizontal whitespace from the end of each +line. If the last line of the range is also the last line of the buffer, strip +trailing blank or whitespace-only lines as well. - *(StripTrailingWhitespace)* -The single mapping target provided is |(StripTrailingWhitespace)|, -mappable in any mode. There is no default key mapping to the target; you -should define this yourself in your |vimrc|. For example: -> - nmap x (StripTrailingWhitespace) -< AUTHOR *strip_trailing_whitespace-author* Written and maintained by Tom Ryder . diff --git a/plugin/strip_trailing_whitespace.vim b/plugin/strip_trailing_whitespace.vim index de08fae..75438d9 100644 --- a/plugin/strip_trailing_whitespace.vim +++ b/plugin/strip_trailing_whitespace.vim @@ -1,6 +1,7 @@ " -" strip_trailing_whitespace.vim: User-defined key mapping to strip trailing -" whitespace in the whole document. +" strip_trailing_whitespace.vim: User command to strip both horizontal and +" vertical whitespace in a buffer, with optional range, reporting both +" accurately and restoring the cursor afterwards. " " Author: Tom Ryder " License: Same as Vim itself @@ -8,88 +9,115 @@ if exists('g:loaded_strip_trailing_whitespace') || &compatible finish endif -if v:version < 600 +if !has('user_commands') || v:version < 600 finish endif let g:loaded_strip_trailing_whitespace = 1 -" Define function for stripping whitespace -function! s:StripTrailingWhitespace() +" Wrapper function to strip both horizontal and vertical trailing whitespace, +" return the cursor to its previous position, and report changes +function s:Strip(start, end) abort - " Line number of last line that had non-whitespace characters on it - let l:cutoff = 1 + " Save cursor position + let l:line = line('.') + let l:col = col('.') - " Tracking lines trimmed between non-whitespace lines and then totalling - let l:trimmed_buffer = 0 - let l:trimmed = 0 + " Strip horizontal space + let l:horizontal = s:StripHorizontal(a:start, a:end) + let l:msg = l:horizontal.' trimmed' + let l:changed = l:horizontal > 0 - " Line number of the file's last line - let l:last = line('$') + " If we're going to the end, strip vertical space + if a:end == line('$') + let l:vertical = s:StripVertical() + let l:msg = l:msg.', '.l:vertical.' deleted' + let l:changed = l:changed || l:vertical > 0 + endif - " Iterate over the lines - let l:li = 1 - while l:li <= l:last + " Return the cursor + call s:Cursor(l:line, l:col) - " Get the line text - let l:line = getline(l:li) + " Report what changed + echomsg l:msg - " If the current line contains trailing whitespace, substitute it out - if l:line =~# '\s\+$' - call setline(l:li, substitute(l:line, '\s\+$', '', '')) - let l:trimmed_buffer = l:trimmed_buffer + 1 - endif + " Return whether anything changed + return l:changed + +endfunction - " If this line has any non-whitespace characters on it, update our cutoff - " point using its index, and push the trimmed lines we've counted since - " the last non-whitespace line onto the trimmed total - if l:line =~# '\S' || l:last == 1 - let l:cutoff = l:li - let l:trimmed = l:trimmed + l:trimmed_buffer - let l:trimmed_buffer = 0 +" Strip horizontal trailing whitespace, return the number of lines changed +function s:StripHorizontal(start, end) abort + + " Start a count of lines trimmed + let l:count = 0 + + " Iterate through buffer + let l:num = 1 + while l:num <= line('$') + + " If the line has trailing whitespace, strip it off and bump the count + let l:line = getline(l:num) + if l:line =~# '\s\+$' + call setline(l:num, substitute(l:line, '\s*$', '', '')) + let l:count = l:count + 1 endif - " Increment the line counter for the next iteration - let l:li = l:li + 1 + " Bump for next iteration + let l:num = l:num + 1 endwhile - " If the last non-whitespace line was before the last line proper, we can - " delete all lines after it - let l:deleted = 0 - if l:cutoff < l:last - - " Get the current line and column so we can return to it - " (Yes I know about winsaveview() and winrestview(); I want this to work - " even on very old versions of Vim if possible) - let l:cursor_line = line('.') - let l:cursor_col = col('.') - - " Delete the rest of the lines, which will move the cursor - silent execute l:cutoff + 1 . ',$ delete _' - - " Return the cursor to the saved position (Vim 6.0 fallback) - if exists('*cursor') - call cursor(l:cursor_line, l:cursor_col) - else - execute 'normal! ' - \ . l:cursor_line . 'G' - \ . l:cursor_col . '|' + " Return the number of lines trimmed + return l:count + +endfunction + +" Strip trailing vertical whitespace, return the number of lines changed +function s:StripVertical() abort + + " Store the number of the last line we found with non-whitespace characters + " on it; start at 1 because even if it's empty it's never trailing + let l:eof = 1 + + " Iterate through buffer + let l:num = 1 + while l:num <= line('$') + + " If the line has any non-whitespace characters in it, update our pointer + " to the end of the file text + let l:line = getline(l:num) + if l:line =~# '\S' + let l:eof = l:num endif - " Record the number of lines deleted - let l:deleted = l:last - l:cutoff + " Bump for next iteration + let l:num = l:num + 1 + endwhile + + " Get the number of lines to delete; if there are any, build a range and + " remove them with :delete, suppressing its normal output (we'll do it) + let l:count = line('$') - l:eof + if l:count + let l:range = (l:eof + 1).',$' + silent execute l:range.'delete' endif - " Print what we did - echomsg l:trimmed . ' trimmed, ' . l:deleted . ' deleted' + " Return the number of lines deleted + return l:count - " Return the number of affected lines - return l:trimmed + l:deleted +endfunction +" Position the cursor; use cursor() if we have it, :normal if not (Vim 6.0) +function s:Cursor(line, col) abort + if exists('*cursor') + return cursor(a:line, a:col) + else + execute 'normal! '.a:line.'G'.a:col.'|' + return 1 + endif endfunction -" Create mapping proxy to the function just defined -nnoremap - \ (StripTrailingWhitespace) - \ :call StripTrailingWhitespace() +" User command for the above +command! -range=% StripTrailingWhitespace + \ call Strip(, ) -- cgit v1.2.3