[
  {
    "path": ".gitignore",
    "content": "doc/tags\ntest/vader"
  },
  {
    "path": ".travis.yml",
    "content": "language: vim\n\nbefore_script: |\n  git clone https://github.com/junegunn/vader.vim.git\n\nscript: |\n  vim -Nu <(cat << VIMRC\n  filetype off\n  set rtp+=vader.vim\n  set rtp+=.\n  set rtp+=after\n  filetype plugin indent on\n  VIMRC) -c 'Vader! test/*.vader' > /dev/null"
  },
  {
    "path": "README.md",
    "content": "# En Masse [![Build Status][travis-image]][travis]\n\n[![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)\n\nTakes 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.\n\n![Animated demonstration](./images/example.gif)\n\n## Using the plugin\n\nAs 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.\n\nYou 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]\n\n## Installation\n\n### [vim-plug](https://github.com/junegunn/vim-plug#readme)\n\nadd this line to `.vimrc`\n\n```\nPlug 'Olical/vim-enmasse'\n```\n\n### [vim-pathogen](https://github.com/tpope/vim-pathogen#readme)\n\n```\ncd ~/.vim/bundle\ngit clone https://github.com/Olical/vim-enmasse\n```\n\n### [Vundle.vim](https://github.com/gmarik/Vundle.vim#readme)\n\nadd this line to `.vimrc`\n\n```\nPlugin 'Olical/vim-enmasse'\n```\n\n## Tests\n\nTests 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.\n\n## Author\n\n[Oliver Caldwell][author-site] ([@OliverCaldwell][author-twitter])\n\n## Unlicenced\n\nFind the full [unlicense][] in the `UNLICENSE` file, but here's a snippet.\n\n>This is free and unencumbered software released into the public domain.\n>\n>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.\n\nDo what you want. Learn as much as you can. Unlicense more software.\n\n[unlicense]: http://unlicense.org/\n[author-site]: http://oli.me.uk/\n[author-twitter]: https://twitter.com/OliverCaldwell\n[jshint]: https://github.com/walm/jshint.vim\n[ag]: https://github.com/rking/ag.vim\n[docs]: https://github.com/Olical/vim-enmasse/blob/master/doc/enmasse.txt\n[travis-image]: https://travis-ci.org/Olical/vim-enmasse.svg?branch=master\n[travis]: https://travis-ci.org/Olical/vim-enmasse\n[vader]: https://github.com/junegunn/vader.vim\n"
  },
  {
    "path": "UNLICENSE",
    "content": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <http://unlicense.org>\n"
  },
  {
    "path": "autoload/enmasse.vim",
    "content": "function! enmasse#Open()\n  let list = s:GetQuickfixList()\n  let sourceLines = s:GetSourceLinesFromList(list)\n\n  if len(list) > 0 && len(sourceLines) > 0\n    call s:CreateEnMasseBuffer(list, sourceLines)\n  else\n    call s:EchoError(\"No entries to edit.\")\n  endif\nendfunction\n\nfunction! enmasse#GetVersion()\n  return \"1.1.1\"\nendfunction\n\nfunction! enmasse#WriteCurrentBuffer()\n  let list = b:enMasseList\n  let sourceLines = getline(1, \"$\")\n\n  if len(list) ==# len(sourceLines)\n    call s:WriteSourceLinesAgainstList(list, sourceLines)\n  else\n    call s:EchoError(\"Mismatch between buffer lines and quickfix list. Refusing to write.\")\n  endif\nendfunction\n\nfunction! enmasse#DisplayQuickfixEntryForCurrentLine()\n  let quickfixItem = s:GetQuickfixItemForCurrentLine()\n  call s:EchoTruncated(quickfixItem.text)\nendfunction\n\nfunction! s:EchoTruncated(msg)\n  let saved=&shortmess\n  set shortmess+=T\n  exec \"echomsg a:msg\"\n  let &shortmess=saved\nendfunction\n\nfunction! s:EchoError(message)\n  echohl ErrorMsg\n  echo \"EnMasse:\" a:message\n  echohl None\nendfunction\n\nfunction! s:GetQuickfixList()\n  let list = getqflist()\n  let uniqueList = []\n\n  for item in list\n    let existingItem = s:GetMatchingLineFromQuickfix(item, uniqueList)\n\n    if has_key(existingItem, \"bufnr\")\n      let existingItem.text = join([existingItem.text, item.text], \" | \")\n    else\n      call add(uniqueList, item)\n    endif\n  endfor\n\n  call sort(uniqueList, \"s:SortByBufferAndLine\")\n\n  return uniqueList\nendfunction\n\nfunction! s:SortByBufferAndLine(i1, i2)\n  if a:i1.bufnr > a:i2.bufnr || (a:i1.bufnr ==# a:i2.bufnr && a:i1.lnum > a:i2.lnum)\n    return 1\n  else\n    return -1\n  endif\nendfunction\n\nfunction! s:GetMatchingLineFromQuickfix(target, list)\n  for item in a:list\n    if a:target.bufnr ==# item.bufnr && a:target.lnum ==# item.lnum\n      return item\n    endif\n  endfor\n\n  return {}\nendfunction\n\nfunction! s:GetSourceLinesFromList(list)\n  let sourceLines = []\n\n  for item in a:list\n    let file = bufname(item.bufnr)\n    let line = item.lnum\n    call add(sourceLines, s:GetLineFromFile(file, line))\n  endfor\n\n  return sourceLines\nendfunction\n\nfunction! s:GetLineFromFile(file, line)\n  let lines = readfile(a:file, \"b\")\n  return lines[a:line - 1]\nendfunction\n\nfunction! s:CreateEnMasseBuffer(list, sourceLines)\n  noautocmd keepalt botright new! __EnMasse__\n  setlocal stl=\\ EnMasse\n  setlocal buftype=acwrite\n  setlocal bufhidden=hide\n  setlocal noswapfile\n  setlocal nobuflisted\n  normal! gg\"_dG\n  call setbufvar(bufnr(''), \"enMasseList\", a:list)\n  call append(0, a:sourceLines)\n  normal! \"_ddgg\n  nnoremap <silent><buffer> <CR> :call <SID>OpenLineInPreviewWindow()<CR>\n  set nomodified\n  if line('$') < winheight(winnr())\n      execute 'resize' line('$')\n  end\nendfunction\n\nfunction! s:OpenLineInPreviewWindow()\n  let quickfixItem = s:GetQuickfixItemForCurrentLine()\n  let file = bufname(quickfixItem.bufnr)\n  execute printf(\"pedit +%d %s\", quickfixItem.lnum, file)\nendfunction\n\nfunction! s:GetQuickfixItemForCurrentLine()\n  let list = b:enMasseList\n  let currentLine = line(\".\")\n  let quickfixItem = list[currentLine - 1]\n  return quickfixItem\nendfunction\n\nfunction! s:WriteSourceLinesAgainstList(list, sourceLines)\n  let toWrite = s:MergeChangesUnderPaths(a:list, a:sourceLines)\n\n  for [filePath, fileChanges] in items(toWrite)\n    let lines = readfile(filePath, \"b\")\n    let changed = 0\n\n    for lineChange in fileChanges\n      if lines[lineChange.line] !=# lineChange.change\n        let lines[lineChange.line] = lineChange.change\n        let changed = 1\n      endif\n    endfor\n\n    if changed\n      execute \"silent doautocmd FileWritePre \" . filePath\n      call writefile(lines, filePath, \"b\")\n      execute \"silent doautocmd FileWritePost \" . filePath\n    endif\n  endfor\n\n  set nomodified\n  checktime\nendfunction\n\nfunction! s:MergeChangesUnderPaths(list, sourceLines)\n  let index = 0\n  let paths = {}\n\n  for item in a:list\n    let path = bufname(item.bufnr)\n    let changes = get(paths, path, [])\n    let paths[path] = add(changes, {\"change\": a:sourceLines[index], \"line\": item.lnum - 1})\n    let index += 1\n  endfor\n\n  return paths\nendfunction\n"
  },
  {
    "path": "doc/enmasse.txt",
    "content": "*enmasse.txt*\n\n            =======================================================\n                   ____  __ _    _  _   __   ____  ____  ____\n                  (  __)(  ( \\  ( \\/ ) / _\\ / ___)/ ___)(  __)\n                   ) _) /    /  / \\/ \\/    \\\\___ \\\\___ \\ ) _)\n                  (____)\\_)__)  \\_)(_/\\_/\\_/(____/(____/(____)\n\n              Edit every file in a quickfix list at the same time.\n            =======================================================\n\n===============================================================================\nIntroduction                                       *enmasse* *enmasse-introduction*\n\nTakes a |quickfix| list and makes it editable. You can then write each change\nback to their respective files using your favourite way of writing files, |:w|\nor |ZZ|, for example. Fix multiple linting issues at once or perform a complex\nfind and replace across your project all within the comfort of Vim.\n\n===============================================================================\nUsage                                                             *enmasse-usage*\n\nAll you have to do is populate a quickfix list in some way (using JSHint or Ag,\nfor example), then execute :EnMasse. This will open a new buffer with each line\ncorresponding to a line in the quickfix list.\n\nYou can then edit each line in any way you want. When done just write this\nmagical buffer and it will update each line in their corresponding files. Do\nnot delete or create any new lines, that will not work, EnMasse will prevent\nyou from writing if it spots a discrepancy because it no longer knows which\nlines should go where.\n\nPressing enter on a line will open the preview window to that line so you can\nget the context of what you're about to edit. This mimics the functionality of\nthe quickfix list.\n\n===============================================================================\nAutocommands                                               *enmasse-autocommands*\n\nWhen writing changes to files, EnMasse will batch writes together. That means\nthat if you have multiple changes for one file, only one write will take place.\nWith this in mind, EnMasse will fire |FileWritePre| and |FileWritePost| for\neach file that is changed.\n\n===============================================================================\nQuickfix hints                                           *enmasse-quickfix-hints*\n\nAs you move your cursor through the lines the matching quickfix entry message\nwill be echoed at the bottom of the screen. So if you're scrolling through a\nbuffer created from a JSHint quickfix list, you'll be provided with the\ncorresponding JSHint message for each line at the bottom of the window.\n\nIf there were multiple quickfix entries for a single line (missing semi-colon\nand unused variable, for example) then their messages will be merged into one\nin the hint. If the message is too long to fit on one line it will be\ntruncated. It's either that or you have a \"press enter to continue\" prompt pop\nup every time the echo wraps onto the next line. Not cool. So truncation is the\nbetter alternative, even if you lose a bit of information sometimes.\n\n===============================================================================\nAuthor                                                           *enmasse-author*\n\nOliver Caldwell <http://oli.me.uk/> / @OliverCaldwell\n\n===============================================================================\nUnlicence                                                     *enmasse-unlicence*\n\nThis is free and unencumbered software released into the public domain. For\nmore information, please refer to <http://unlicense.org/> or the \"README\" of\nthis project.\n\nvim:tw=78:sw=4:ts=4:ft=help:norl:\n"
  },
  {
    "path": "plugin/enmasse.vim",
    "content": "command! EnMasse :call enmasse#Open()\ncommand! EnMasseVersion :echo enmasse#GetVersion()\n\naugroup EnMasseDefault\n  autocmd!\n  autocmd WinLeave __EnMasse__ wincmd p\n  autocmd BufWriteCmd __EnMasse__ call enmasse#WriteCurrentBuffer()\n  autocmd CursorMoved __EnMasse__ call enmasse#DisplayQuickfixEntryForCurrentLine()\naugroup END\n"
  },
  {
    "path": "test/clearing.vader",
    "content": "Execute (the line counts are correct):\n  silent grep! -i a test/grepable.txt\n  EnMasse\n  let before = line(\"$\")\n  normal G\n  quit\n  silent grep! -i b test/grepable.txt\n  EnMasse\n  let after = line(\"$\")\n\n  AssertEqual 5, before\n  AssertEqual 2, after\n"
  },
  {
    "path": "test/enmasse.vader",
    "content": "Before (read the example grepable file and grep for quickfix):\n  let lines = readfile(\"test/grepable.txt\", \"b\")\n  silent grep! quickfix test/grepable.txt\n\nExecute (can't call :EnMasse without a quickfix list):\n  call setqflist([])\n\n  redir => messages\n  EnMasse\n  redir END\n\n  let result = get(split(messages, \"\\n\"), -1, \"\")\n\n  AssertEqual \"EnMasse: No entries to edit.\", result\n\nExecute (:EnMasse with a quickfix list creates a buffer):\n  EnMasse\n  let name = bufname(\"%\")\n  quit\n\n  AssertEqual \"__EnMasse__\", name\n\nExecute (the buffer contains the correct line from the quickfix list):\n  EnMasse\n  let firstLine = getline(\"1\")\n  quit\n\n  AssertEqual lines[1], firstLine\n\nExecute (duplicate quickfix lines are joined together):\n  silent grepadd! loaded test/grepable.txt\n  EnMasse\n  let firstLine = getline(\"1\")\n  let secondLine = getline(\"2\")\n  let lineCount = line(\"$\")\n  quit\n\n  AssertEqual lines[1], firstLine\n  AssertEqual lines[3], secondLine\n  AssertEqual 2, lineCount\n"
  },
  {
    "path": "test/grepable.txt",
    "content": "This is an example file.\nIt should be loaded into the quickfix list.\nThen you can run :EnMasse\nAnd edit that quickfix list.\nAnd 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.\nDupe!\n"
  },
  {
    "path": "test/hints.vader",
    "content": "Before (read the example grepable file and grep for quickfix):\n  let lines = readfile(\"test/grepable.txt\", \"b\")\n  silent grep! quickfix test/grepable.txt\n\nExecute (when the cursor is moved, the hint updates):\n  EnMasse\n  normal j\n\n  redir => messages\n  doautocmd CursorMoved\n  redir END\n\n  quit\n  let result = get(split(messages, \"\\n\"), -1, \"\")\n\n  AssertEqual lines[3], result\n\nExecute (when lines have been joined, the hint contains both of the results seperated with a pipe):\n  silent grep! \"Dupe!\" test/grepable.txt\n  silent grepadd! \"Dupe!\" test/grepable.txt\n  EnMasse\n  normal jk\n\n  redir => messages\n  doautocmd CursorMoved\n  redir END\n\n  quit\n  let result = get(split(messages, \"\\n\"), -1, \"\")\n\n  AssertEqual join([lines[5], lines[5]], \" | \"), result"
  },
  {
    "path": "test/preview.vader",
    "content": "Before (set up an EnMasse buffer):\n  let lines = readfile(\"test/grepable.txt\", \"b\")\n  silent grep! quickfix test/grepable.txt\n  EnMasse\n\nExecute (hitting enter on a line opens it in the preview window):\n  let before = getline(\".\")\n  execute \"normal \\<CR>\\<C-W>k\"\n  let after = getline(\".\")\n  let bufferName = expand(\"%\")\n  AssertEqual \"test/grepable.txt\", bufferName\n  AssertEqual before, after\n  pclose"
  },
  {
    "path": "test/run",
    "content": "#!/usr/bin/env bash\n\nif [[ ! -d test/vader ]]; then\n    git clone https://github.com/junegunn/vader.vim.git test/vader\nelse\n    pushd test/vader\n    git pull\n    popd\nfi\n\nvim -Nu <(cat << VIMRC\nfiletype off\nset rtp=.\nset rtp+=test/vader\nfiletype plugin indent on\nVIMRC\n) -c 'Vader! test/*.vader' > /dev/null"
  },
  {
    "path": "test/version.vader",
    "content": "Before (set up regular expression):\n  let versionRegExp = '\\v\\d+\\.\\d+\\.\\d+'\n\nExecute (can print the version number with the command):\n  redir => messages\n  EnMasseVersion\n  redir END\n\n  let result = get(split(messages, \"\\n\"), -1, \"\")\n\n  Assert result =~# versionRegExp\n\nExecute (can get the version number with the function):\n  Assert enmasse#GetVersion() =~# versionRegExp"
  },
  {
    "path": "test/writing.vader",
    "content": "Before (define test data and setup EnMasse):\n  let original = [\"EnMasse is cOoL\", \"Hello, World!\", \"This is EnMasse.\", \"EnMasse is useful.\"]\n  let expected = [\"EnMasse is Cool\", \"Hello, World!\", \"This is EnMasse, a Vim plugin.\", \"EnMasse is handy.\"]\n  let filePath = tempname()\n  set nomodified\n  call writefile(original, filePath)\n  execute \"silent grep! EnMasse \" . filePath\n  EnMasse\n\nAfter (remove the temporary file and close the previous EnMasse buffer):\n  call delete(filePath)\n  quit!\n\nExecute (editing and writing in an EnMasse buffer changes the file):\n  %s/is EnMasse/is EnMasse, a Vim plugin/\n  %s/useful/handy/\n  %s/cOoL/Cool/\n  set ignorecase\n  write\n  set noignorecase\n  let actual = readfile(filePath)\n  AssertEqual expected, actual\n\nExecute (will not let you write if a line is deleted):\n  normal dd\n  redir => messages\n  write\n  redir END\n  let actual = readfile(filePath)\n  let latestMessage = get(split(messages, \"\\n\"), -1, \"\")\n  AssertEqual original, actual\n  AssertEqual \"EnMasse: Mismatch between buffer lines and quickfix list. Refusing to write.\", latestMessage\n\nExecute (will not let you write if a line is added):\n  normal o\n  redir => messages\n  write\n  redir END\n  let actual = readfile(filePath)\n  let latestMessage = get(split(messages, \"\\n\"), -1, \"\")\n  AssertEqual original, actual\n  AssertEqual \"EnMasse: Mismatch between buffer lines and quickfix list. Refusing to write.\", latestMessage\n\nExecute (doesn't write if no lines have changed):\n  let before = getftime(filePath)\n  write\n  let after = getftime(filePath)\n  AssertEqual before, after\n\nExecute (changing a file that you have open will prompt for a reload):\n  execute \"split \" . filePath\n  %s/is EnMasse/is EnMasse, a Vim plugin/\n  %s/useful/handy/\n  write\n  execute \"normal! l\\<CR>\\<C-W>j\"\n  let bufferLines = getline(1, \"$\")\n  let actual = readfile(filePath)\n  AssertEqual actual, bufferLines\n\nExecute (multiple changes to one file are written in one batched write):\n  %s/is EnMasse/is EnMasse, a Vim plugin/\n  %s/useful/handy/\n  let writes = 0\n  let actual = readfile(filePath)\n  autocmd FileWritePost * let writes += 1\n  write\n  AssertEqual 1, writes\n"
  }
]