From 74245fca4c3f5b81a45b93d8a40d7716415b531c Mon Sep 17 00:00:00 2001 From: dfishburn Date: Mon, 8 Jun 2015 13:20:08 -0400 Subject: [PATCH 1/2] Vim plugin checks and location list The JSON formatter supports linewize and visualwise Vim ranges allowing the user to choose what to format. A separate Vim window / buffer is opened to display messages and error messages. The location and size of that window is configurable. Better error and reporting is displayed after a format of the json from the node.js module (changes on that side are also required). Changes integrated into the Vim location list, so that you can click directly on a line and it will take you to where the error is. This is the same features of Vim used for the :grep and :make commands. --- plugin/json_formatter.vim | 214 +++++++++++++++++++++++++++++++++++++- 1 file changed, 209 insertions(+), 5 deletions(-) diff --git a/plugin/json_formatter.vim b/plugin/json_formatter.vim index 49b522b..eef0426 100644 --- a/plugin/json_formatter.vim +++ b/plugin/json_formatter.vim @@ -1,10 +1,214 @@ -if exists("loaded_json_formatter") - finish +if exists("g:loaded_json_formatter") + finish endif -function! JsonFormatter() - execute "%!jjson --vim-plugin-mode -i 4 -f %" +let g:loaded_json_formatter = 1 + +let s:json_formatter_buffer_errors = 0 +let s:json_formatter_buffer_last = 0 +let s:json_formatter_buffer_last_winnr = 0 + +" Get the name of a temporary file for the system +let s:json_formatter_tempfile = fnamemodify(tempname(), ":h") +let s:json_formatter_tempfile = s:json_formatter_tempfile.(s:json_formatter_tempfile =~ '^/' ? '/' : '\').'json_formatter.json' + +" define shell command +if !exists('g:json_formatter_command') + let g:json_formatter_command = 'jjson' +endif + +" Error window position and size +if !exists('g:json_formatter_window_title') + let g:json_formatter_window_title = 'JSON Formatter Errors' +endif +if !exists('g:json_formatter_window_use_horiz') + let g:json_formatter_window_use_horiz = 1 +endif +if !exists('g:json_formatter_window_use_bottom') + let g:json_formatter_window_use_bottom = 1 +endif +if !exists('g:json_formatter_window_use_right') + let g:json_formatter_window_use_right = 0 +endif +if !exists('g:json_formatter_window_width') + let g:json_formatter_window_width = 80 +endif +if !exists('g:json_formatter_window_increment') + let g:json_formatter_window_increment = 50 +endif + +function! s:JSONFormatter(...) range + let default_register = 'a' + let default_register_type = 'V' + let save_reg = getreg(default_register) + let save_reg_type = getregtype(default_register) + let linenum = 1 + let colnum = 1 + + " Default command mode to normal mode 'n' + let cmd_mode = 'n' + if a:0 > 0 + " Change to visual mode, if command executed via a visual map + let cmd_mode = ((a:1 == 'v') ? 'v' : 'n') + endif + + if cmd_mode == 'v' + " We are yanking either an entire line, or a range. + " Reselect the visual range and yank the text + " into our register. + silent! exec 'normal! gv"' . default_register . 'y' + let default_register_type = getregtype(default_register) + let orig_linenum = line("'<") + let orig_colnum = colnum("'<") + let end_linenum = line("'>") + let linenum = line("'<") + let colnum = colnum("'<") + else + " In normal mode, always yank the complete line, since this + " command is for a range. + silent! exec a:firstline . ',' . a:lastline . 'yank '. default_register + let orig_linenum = a:firstline + let orig_colnum = 1 + let end_linenum = a:lastline + let linenum = a:firstline + let colnum = 1 + endif + + " Populate the temporary file with the yanked text + let rc = writefile(split(getreg(default_register), "\n"), s:json_formatter_tempfile) + if rc == -1 + echohl Warning + echo 'JSONFormatter - Failed to write to temporary file[' . s:json_formatter_tempfile . ']' + echohl None + return + endif + + " Store buffer information to return to for the error list + let s:json_formatter_buffer_last = bufnr('%') + let s:json_formatter_buffer_last_winnr = winnr() + + " Use nodejs to format the JSON + let cmd = shellescape(g:json_formatter_command)." -i 4 -f " . s:json_formatter_tempfile + let result = system( cmd ) + + echomsg result + " If the formatting failed, the result always starts with "Error occurred" + " so check for this text. + if result =~ '^Error occurred while parsing file' + " Handle strings like this: + " Error occurred while parsing file: + " D:\WINDOW~1\json_formatter.json,3,130,found: '}' - expected: 'STRING' + let matches = matchlist(result, '^Error occurred while parsing file:\s\+\zs\([^,]\+\),\(\d\+\),\(\d\+\),\(.*\)', '', '') + + let MATCH_ALL = 0 + let MATCH_FILENAME = 1 + let MATCH_LINENUM = 2 + let MATCH_COLNUM = 3 + let MATCH_ERROR = 4 + + " Open the quick fix window with an errorformat specified. + setlocal errorformat=%E%f,%l,%c,%Z%m + + if len(matches) > 3 && matches[MATCH_COLNUM] != '' + + let linenum = linenum + matches[MATCH_LINENUM] - 1 + + for line in getbufline('', orig_linenum, linenum) + " Empty lines are not picked up by the JSON parser + " so manually adjust the linenum. + " Abort at the first non-empty line. + if line =~ '^$' + let linenum = linenum + 1 + else + break + endif + endfor + + if 1 == matches[MATCH_LINENUM] && colnum != 1 + " A range of lines was selected so we need to correct the + " offset to the errorline so the quickfix window will take + " us to the correct column. + let colnum = colnum + matches[MATCH_COLNUM] - 1 + else + let colnum = matches[MATCH_COLNUM] + endif + + let rc = setqflist([ + \{ + \ 'bufnr': '' + \, 'filename': bufname(s:json_formatter_buffer_last) + \, 'lnum': linenum + \, 'pattern': '' + \, 'col': colnum + \, 'vcol': 0 + \, 'nr': 0 + \, 'text': matches[MATCH_ERROR] + \, 'type': 'E' + \} + \,] + \) + else + let linenum = orig_linenum + + for line in getbufline('', orig_linenum, end_linenum) + " Blank lines are not picked up by the JSON parser + " so manually adjust the linenum. + if line =~ '^\s*$' + let linenum = linenum + 1 + else + break + endif + endfor + + " Either JSONLint hasn't been installed or JSONLint passed + " but JSON.parse() has failed. So no line and column information + " is provided. Just show the error message. + let rc = setqflist([ + \{ + \ 'bufnr': '' + \, 'filename': bufname(s:json_formatter_buffer_last) + \, 'lnum': linenum + \, 'pattern': '' + \, 'col': colnum + \, 'vcol': 0 + \, 'nr': -1 + \, 'text': matchstr(result, '^Error occurred while parsing file: \zs.*', '', '') + \, 'type': 'E' + \} + \,] + \) + endif + + copen + else + " Put the formatted JSON into our register. + call setreg(default_register, result, default_register_type) + + " The formatting was successful, replace the selected text + " with the formatted text. + if cmd_mode == 'v' + " Reselect the visual selection and paste the newly + " formatted text + silent! exec 'normal! gv"' . default_register . 'p' + else + " In normal mode, always yank the complete line, since this + " command is for a range. + silent! exec a:firstline . ',' . a:lastline . 'delete' + " Replaced selected area with reformatted JSON from the default + " register. + " Subtract 1 from the firstline of the range since we just + " deleted those lines, so we need to put from the previous line. + silent! exec (a:firstline - 1) . 'put ' . default_register + endif + endif + + call setreg(default_register, save_reg, save_reg_type) endfunction -nnoremap json :call JsonFormatter() +command! -nargs=? -range=% JSONFormatter ,call s:JSONFormatter() + +"exec 'xnoremap '.g:yankring_v_key." :JSONFormatter 'v'" + +"xmap