Repository: Olical/vim-enmasse Branch: master Commit: c2286f1d7bd7 Files: 15 Total size: 17.8 KB Directory structure: gitextract_gubi8ydj/ ├── .gitignore ├── .travis.yml ├── README.md ├── UNLICENSE ├── autoload/ │ └── enmasse.vim ├── doc/ │ └── enmasse.txt ├── plugin/ │ └── enmasse.vim └── test/ ├── clearing.vader ├── enmasse.vader ├── grepable.txt ├── hints.vader ├── preview.vader ├── run ├── version.vader └── writing.vader ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ doc/tags test/vader ================================================ FILE: .travis.yml ================================================ language: vim before_script: | git clone https://github.com/junegunn/vader.vim.git script: | vim -Nu <(cat << VIMRC filetype off set rtp+=vader.vim set rtp+=. set rtp+=after filetype plugin indent on VIMRC) -c 'Vader! test/*.vader' > /dev/null ================================================ FILE: README.md ================================================ # En Masse [![Build Status][travis-image]][travis] [![Join the chat at https://gitter.im/Wolfy87/vim-enmasse](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Wolfy87/vim-enmasse?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Takes a quickfix list and makes it editable. You can then write each change back to their respective files using your favourite way of writing files, `:w` or `ZZ`, for example. Fix multiple [JSHint][] issues at once or perform a complex find and replace across your project all within the comfort of Vim. ![Animated demonstration](./images/example.gif) ## Using the plugin As you can see in the demonstration above, all you have to do is populate a quickfix list in some way (I used [JSHint][], but you could use [Ag][], for example), then execute `:EnMasse`. This will open a new buffer with each line corresponding to a line in the quickfix list. You can then edit each line in any way you want. When done just write this magical buffer and it will update each line in their corresponding files. For more information, check out [the documentation!][docs] ## Installation ### [vim-plug](https://github.com/junegunn/vim-plug#readme) add this line to `.vimrc` ``` Plug 'Olical/vim-enmasse' ``` ### [vim-pathogen](https://github.com/tpope/vim-pathogen#readme) ``` cd ~/.vim/bundle git clone https://github.com/Olical/vim-enmasse ``` ### [Vundle.vim](https://github.com/gmarik/Vundle.vim#readme) add this line to `.vimrc` ``` Plugin 'Olical/vim-enmasse' ``` ## Tests Tests are performed using [vader][], to pull the dependencies and run them simply execute `./tests/run`. The tests are automatically executed by [TravisCI][travis] too, so keep an eye on that if you push changes or open a PR. The badge up the top of this README indicates the state of master, it should ALWAYS be green. A test should be written before any change is made. ## Author [Oliver Caldwell][author-site] ([@OliverCaldwell][author-twitter]) ## Unlicenced Find the full [unlicense][] in the `UNLICENSE` file, but here's a snippet. >This is free and unencumbered software released into the public domain. > >Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. Do what you want. Learn as much as you can. Unlicense more software. [unlicense]: http://unlicense.org/ [author-site]: http://oli.me.uk/ [author-twitter]: https://twitter.com/OliverCaldwell [jshint]: https://github.com/walm/jshint.vim [ag]: https://github.com/rking/ag.vim [docs]: https://github.com/Olical/vim-enmasse/blob/master/doc/enmasse.txt [travis-image]: https://travis-ci.org/Olical/vim-enmasse.svg?branch=master [travis]: https://travis-ci.org/Olical/vim-enmasse [vader]: https://github.com/junegunn/vader.vim ================================================ FILE: UNLICENSE ================================================ This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to ================================================ FILE: autoload/enmasse.vim ================================================ function! enmasse#Open() let list = s:GetQuickfixList() let sourceLines = s:GetSourceLinesFromList(list) if len(list) > 0 && len(sourceLines) > 0 call s:CreateEnMasseBuffer(list, sourceLines) else call s:EchoError("No entries to edit.") endif endfunction function! enmasse#GetVersion() return "1.1.1" endfunction function! enmasse#WriteCurrentBuffer() let list = b:enMasseList let sourceLines = getline(1, "$") if len(list) ==# len(sourceLines) call s:WriteSourceLinesAgainstList(list, sourceLines) else call s:EchoError("Mismatch between buffer lines and quickfix list. Refusing to write.") endif endfunction function! enmasse#DisplayQuickfixEntryForCurrentLine() let quickfixItem = s:GetQuickfixItemForCurrentLine() call s:EchoTruncated(quickfixItem.text) endfunction function! s:EchoTruncated(msg) let saved=&shortmess set shortmess+=T exec "echomsg a:msg" let &shortmess=saved endfunction function! s:EchoError(message) echohl ErrorMsg echo "EnMasse:" a:message echohl None endfunction function! s:GetQuickfixList() let list = getqflist() let uniqueList = [] for item in list let existingItem = s:GetMatchingLineFromQuickfix(item, uniqueList) if has_key(existingItem, "bufnr") let existingItem.text = join([existingItem.text, item.text], " | ") else call add(uniqueList, item) endif endfor call sort(uniqueList, "s:SortByBufferAndLine") return uniqueList endfunction function! s:SortByBufferAndLine(i1, i2) if a:i1.bufnr > a:i2.bufnr || (a:i1.bufnr ==# a:i2.bufnr && a:i1.lnum > a:i2.lnum) return 1 else return -1 endif endfunction function! s:GetMatchingLineFromQuickfix(target, list) for item in a:list if a:target.bufnr ==# item.bufnr && a:target.lnum ==# item.lnum return item endif endfor return {} endfunction function! s:GetSourceLinesFromList(list) let sourceLines = [] for item in a:list let file = bufname(item.bufnr) let line = item.lnum call add(sourceLines, s:GetLineFromFile(file, line)) endfor return sourceLines endfunction function! s:GetLineFromFile(file, line) let lines = readfile(a:file, "b") return lines[a:line - 1] endfunction function! s:CreateEnMasseBuffer(list, sourceLines) noautocmd keepalt botright new! __EnMasse__ setlocal stl=\ EnMasse setlocal buftype=acwrite setlocal bufhidden=hide setlocal noswapfile setlocal nobuflisted normal! gg"_dG call setbufvar(bufnr(''), "enMasseList", a:list) call append(0, a:sourceLines) normal! "_ddgg nnoremap :call OpenLineInPreviewWindow() set nomodified if line('$') < winheight(winnr()) execute 'resize' line('$') end endfunction function! s:OpenLineInPreviewWindow() let quickfixItem = s:GetQuickfixItemForCurrentLine() let file = bufname(quickfixItem.bufnr) execute printf("pedit +%d %s", quickfixItem.lnum, file) endfunction function! s:GetQuickfixItemForCurrentLine() let list = b:enMasseList let currentLine = line(".") let quickfixItem = list[currentLine - 1] return quickfixItem endfunction function! s:WriteSourceLinesAgainstList(list, sourceLines) let toWrite = s:MergeChangesUnderPaths(a:list, a:sourceLines) for [filePath, fileChanges] in items(toWrite) let lines = readfile(filePath, "b") let changed = 0 for lineChange in fileChanges if lines[lineChange.line] !=# lineChange.change let lines[lineChange.line] = lineChange.change let changed = 1 endif endfor if changed execute "silent doautocmd FileWritePre " . filePath call writefile(lines, filePath, "b") execute "silent doautocmd FileWritePost " . filePath endif endfor set nomodified checktime endfunction function! s:MergeChangesUnderPaths(list, sourceLines) let index = 0 let paths = {} for item in a:list let path = bufname(item.bufnr) let changes = get(paths, path, []) let paths[path] = add(changes, {"change": a:sourceLines[index], "line": item.lnum - 1}) let index += 1 endfor return paths endfunction ================================================ FILE: doc/enmasse.txt ================================================ *enmasse.txt* ======================================================= ____ __ _ _ _ __ ____ ____ ____ ( __)( ( \ ( \/ ) / _\ / ___)/ ___)( __) ) _) / / / \/ \/ \\___ \\___ \ ) _) (____)\_)__) \_)(_/\_/\_/(____/(____/(____) Edit every file in a quickfix list at the same time. ======================================================= =============================================================================== Introduction *enmasse* *enmasse-introduction* Takes a |quickfix| list and makes it editable. You can then write each change back to their respective files using your favourite way of writing files, |:w| or |ZZ|, for example. Fix multiple linting issues at once or perform a complex find and replace across your project all within the comfort of Vim. =============================================================================== Usage *enmasse-usage* All you have to do is populate a quickfix list in some way (using JSHint or Ag, for example), then execute :EnMasse. This will open a new buffer with each line corresponding to a line in the quickfix list. You can then edit each line in any way you want. When done just write this magical buffer and it will update each line in their corresponding files. Do not delete or create any new lines, that will not work, EnMasse will prevent you from writing if it spots a discrepancy because it no longer knows which lines should go where. Pressing enter on a line will open the preview window to that line so you can get the context of what you're about to edit. This mimics the functionality of the quickfix list. =============================================================================== Autocommands *enmasse-autocommands* When writing changes to files, EnMasse will batch writes together. That means that if you have multiple changes for one file, only one write will take place. With this in mind, EnMasse will fire |FileWritePre| and |FileWritePost| for each file that is changed. =============================================================================== Quickfix hints *enmasse-quickfix-hints* As you move your cursor through the lines the matching quickfix entry message will be echoed at the bottom of the screen. So if you're scrolling through a buffer created from a JSHint quickfix list, you'll be provided with the corresponding JSHint message for each line at the bottom of the window. If there were multiple quickfix entries for a single line (missing semi-colon and unused variable, for example) then their messages will be merged into one in the hint. If the message is too long to fit on one line it will be truncated. It's either that or you have a "press enter to continue" prompt pop up every time the echo wraps onto the next line. Not cool. So truncation is the better alternative, even if you lose a bit of information sometimes. =============================================================================== Author *enmasse-author* Oliver Caldwell / @OliverCaldwell =============================================================================== Unlicence *enmasse-unlicence* This is free and unencumbered software released into the public domain. For more information, please refer to or the "README" of this project. vim:tw=78:sw=4:ts=4:ft=help:norl: ================================================ FILE: plugin/enmasse.vim ================================================ command! EnMasse :call enmasse#Open() command! EnMasseVersion :echo enmasse#GetVersion() augroup EnMasseDefault autocmd! autocmd WinLeave __EnMasse__ wincmd p autocmd BufWriteCmd __EnMasse__ call enmasse#WriteCurrentBuffer() autocmd CursorMoved __EnMasse__ call enmasse#DisplayQuickfixEntryForCurrentLine() augroup END ================================================ FILE: test/clearing.vader ================================================ Execute (the line counts are correct): silent grep! -i a test/grepable.txt EnMasse let before = line("$") normal G quit silent grep! -i b test/grepable.txt EnMasse let after = line("$") AssertEqual 5, before AssertEqual 2, after ================================================ FILE: test/enmasse.vader ================================================ Before (read the example grepable file and grep for quickfix): let lines = readfile("test/grepable.txt", "b") silent grep! quickfix test/grepable.txt Execute (can't call :EnMasse without a quickfix list): call setqflist([]) redir => messages EnMasse redir END let result = get(split(messages, "\n"), -1, "") AssertEqual "EnMasse: No entries to edit.", result Execute (:EnMasse with a quickfix list creates a buffer): EnMasse let name = bufname("%") quit AssertEqual "__EnMasse__", name Execute (the buffer contains the correct line from the quickfix list): EnMasse let firstLine = getline("1") quit AssertEqual lines[1], firstLine Execute (duplicate quickfix lines are joined together): silent grepadd! loaded test/grepable.txt EnMasse let firstLine = getline("1") let secondLine = getline("2") let lineCount = line("$") quit AssertEqual lines[1], firstLine AssertEqual lines[3], secondLine AssertEqual 2, lineCount ================================================ FILE: test/grepable.txt ================================================ This is an example file. It should be loaded into the quickfix list. Then you can run :EnMasse And edit that quickfix list. And this line is way too long so it will truncated. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin et ornare libero, quis vulputate odio. In luctus id velit sed gravida. Cras turpis nibh, luctus a lorem non, sollicitudin auctor justo. Vivamus tristique dolor a lectus gravida molestie. Nunc varius mi ante, vitae bibendum quam ultricies vitae. Dupe! ================================================ FILE: test/hints.vader ================================================ Before (read the example grepable file and grep for quickfix): let lines = readfile("test/grepable.txt", "b") silent grep! quickfix test/grepable.txt Execute (when the cursor is moved, the hint updates): EnMasse normal j redir => messages doautocmd CursorMoved redir END quit let result = get(split(messages, "\n"), -1, "") AssertEqual lines[3], result Execute (when lines have been joined, the hint contains both of the results seperated with a pipe): silent grep! "Dupe!" test/grepable.txt silent grepadd! "Dupe!" test/grepable.txt EnMasse normal jk redir => messages doautocmd CursorMoved redir END quit let result = get(split(messages, "\n"), -1, "") AssertEqual join([lines[5], lines[5]], " | "), result ================================================ FILE: test/preview.vader ================================================ Before (set up an EnMasse buffer): let lines = readfile("test/grepable.txt", "b") silent grep! quickfix test/grepable.txt EnMasse Execute (hitting enter on a line opens it in the preview window): let before = getline(".") execute "normal \\k" let after = getline(".") let bufferName = expand("%") AssertEqual "test/grepable.txt", bufferName AssertEqual before, after pclose ================================================ FILE: test/run ================================================ #!/usr/bin/env bash if [[ ! -d test/vader ]]; then git clone https://github.com/junegunn/vader.vim.git test/vader else pushd test/vader git pull popd fi vim -Nu <(cat << VIMRC filetype off set rtp=. set rtp+=test/vader filetype plugin indent on VIMRC ) -c 'Vader! test/*.vader' > /dev/null ================================================ FILE: test/version.vader ================================================ Before (set up regular expression): let versionRegExp = '\v\d+\.\d+\.\d+' Execute (can print the version number with the command): redir => messages EnMasseVersion redir END let result = get(split(messages, "\n"), -1, "") Assert result =~# versionRegExp Execute (can get the version number with the function): Assert enmasse#GetVersion() =~# versionRegExp ================================================ FILE: test/writing.vader ================================================ Before (define test data and setup EnMasse): let original = ["EnMasse is cOoL", "Hello, World!", "This is EnMasse.", "EnMasse is useful."] let expected = ["EnMasse is Cool", "Hello, World!", "This is EnMasse, a Vim plugin.", "EnMasse is handy."] let filePath = tempname() set nomodified call writefile(original, filePath) execute "silent grep! EnMasse " . filePath EnMasse After (remove the temporary file and close the previous EnMasse buffer): call delete(filePath) quit! Execute (editing and writing in an EnMasse buffer changes the file): %s/is EnMasse/is EnMasse, a Vim plugin/ %s/useful/handy/ %s/cOoL/Cool/ set ignorecase write set noignorecase let actual = readfile(filePath) AssertEqual expected, actual Execute (will not let you write if a line is deleted): normal dd redir => messages write redir END let actual = readfile(filePath) let latestMessage = get(split(messages, "\n"), -1, "") AssertEqual original, actual AssertEqual "EnMasse: Mismatch between buffer lines and quickfix list. Refusing to write.", latestMessage Execute (will not let you write if a line is added): normal o redir => messages write redir END let actual = readfile(filePath) let latestMessage = get(split(messages, "\n"), -1, "") AssertEqual original, actual AssertEqual "EnMasse: Mismatch between buffer lines and quickfix list. Refusing to write.", latestMessage Execute (doesn't write if no lines have changed): let before = getftime(filePath) write let after = getftime(filePath) AssertEqual before, after Execute (changing a file that you have open will prompt for a reload): execute "split " . filePath %s/is EnMasse/is EnMasse, a Vim plugin/ %s/useful/handy/ write execute "normal! l\\j" let bufferLines = getline(1, "$") let actual = readfile(filePath) AssertEqual actual, bufferLines Execute (multiple changes to one file are written in one batched write): %s/is EnMasse/is EnMasse, a Vim plugin/ %s/useful/handy/ let writes = 0 let actual = readfile(filePath) autocmd FileWritePost * let writes += 1 write AssertEqual 1, writes