diff options
Diffstat (limited to 'autoload')
| -rw-r--r-- | autoload/prettier.vim | 427 | ||||
| -rw-r--r-- | autoload/prettier/bridge/parser.vim | 24 | ||||
| -rw-r--r-- | autoload/prettier/job/async/neovim.vim | 98 | ||||
| -rw-r--r-- | autoload/prettier/job/async/vim.vim | 69 | ||||
| -rw-r--r-- | autoload/prettier/job/runner.vim | 63 | ||||
| -rw-r--r-- | autoload/prettier/logging/error.vim | 12 | ||||
| -rw-r--r-- | autoload/prettier/presets/fb.vim | 13 | ||||
| -rw-r--r-- | autoload/prettier/resolver/config.vim | 98 | ||||
| -rw-r--r-- | autoload/prettier/resolver/executable.vim | 74 | ||||
| -rw-r--r-- | autoload/prettier/resolver/preset.vim | 8 | ||||
| -rw-r--r-- | autoload/prettier/utils/buffer.vim | 70 | ||||
| -rw-r--r-- | autoload/prettier/utils/quickfix.vim | 22 | ||||
| -rw-r--r-- | autoload/prettier/utils/shim.vim | 8 |
13 files changed, 603 insertions, 383 deletions
diff --git a/autoload/prettier.vim b/autoload/prettier.vim index 52e9ea8..d4c3171 100644 --- a/autoload/prettier.vim +++ b/autoload/prettier.vim @@ -5,425 +5,86 @@ " Name Of File: prettier.vim " Description: A vim plugin wrapper for prettier, pre-configured with custom default prettier settings. " Maintainer: Mitermayer Reis <mitermayer.reis at gmail.com> -" Version: 0.2.7 +" Version: 1.0.0-alpha " Usage: Use :help vim-prettier-usage, or visit https://github.com/prettier/vim-prettier " "========================================================================================================== " }}} -let s:root_dir = fnamemodify(resolve(expand('<sfile>:p')), ':h') -let s:prettier_job_running = 0 -let s:prettier_quickfix_open = 0 - +" Displays the resolve prettier CLI path function! prettier#PrettierCliPath() abort - let l:execCmd = s:Get_Prettier_Exec() + let l:execCmd = prettier#resolver#executable#getPath() if l:execCmd != -1 echom l:execCmd else - call s:Suggest_Install_Prettier() + call prettier#logging#error#log('EXECUTABLE_NOT_FOUND_ERROR') endif endfunction +" Allows user commands to be passed straight to the prettier CLI function! prettier#PrettierCli(user_input) abort - let l:execCmd = s:Get_Prettier_Exec() + let l:execCmd = prettier#resolver#executable#getPath() if l:execCmd != -1 let l:out = system(l:execCmd. ' ' . a:user_input) echom l:out else - call s:Suggest_Install_Prettier() + call prettier#logging#error#log('EXECUTABLE_NOT_FOUND_ERROR') endif endfunction +" Allows @format and @prettier pragma support upon saving +function! prettier#Autoformat(...) abort + call prettier#Prettier(1, 1, line('$'), 0, { 'requirePragma': 'true'}) +endfunction + +" Main prettier command function! prettier#Prettier(...) abort - let l:execCmd = s:Get_Prettier_Exec() + let l:execCmd = prettier#resolver#executable#getPath() let l:async = a:0 > 0 ? a:1 : 0 let l:startSelection = a:0 > 1 ? a:2 : 1 let l:endSelection = a:0 > 2 ? a:3 : line('$') - let l:config = getbufvar(bufnr('%'), 'prettier_ft_default_args', {}) + let l:hasSelection = a:0 > 2 ? 1 : 0 + let l:partialFormat = a:0 > 3 && a:4 ? a:4 : 0 + let l:partialFormatEnabled = l:hasSelection && l:partialFormat + + let l:overWrite = a:0 > 4 ? a:5 : {} + let l:bufferConfig = getbufvar(bufnr('%'), 'prettier_ft_default_args', {}) + let l:config = extend(l:bufferConfig, l:overWrite) if l:execCmd != -1 - let l:cmd = l:execCmd . s:Get_Prettier_Exec_Args(l:config) + " TODO + " => we should make sure we can resolve --range-start and --range-end when required + " => when the above is required we should also update l:startSelection to '1' and l:endSelection to line('$') + let l:cmd = l:execCmd . prettier#resolver#config#resolve( + \ prettier#resolver#preset#resolve(l:config), + \ l:partialFormatEnabled, + \ l:startSelection, + \ l:endSelection) " close quickfix if it is opened - if s:prettier_quickfix_open - call setqflist([], 'r') - cclose - let s:prettier_quickfix_open = 0 - endif - - if l:async && v:version >= 800 && exists('*job_start') - call s:Prettier_Exec_Async(l:cmd, l:startSelection, l:endSelection) - elseif l:async && has('nvim') && g:prettier#nvim_unstable_async - call s:Prettier_Exec_Async_Nvim(l:cmd, l:startSelection, l:endSelection) - else - call s:Prettier_Exec_Sync(l:cmd, l:startSelection, l:endSelection) - endif - else - call s:Suggest_Install_Prettier() - endif -endfunction - -function! s:Prettier_Exec_Async_Nvim(cmd, startSelection, endSelection) abort - let l:async_cmd = a:cmd - - if has('win32') || has('win64') - let l:async_cmd = 'cmd.exe /c ' . a:cmd - endif - - let l:lines = getline(a:startSelection, a:endSelection) - let l:dict = { - \ 'start': a:startSelection - 1, - \ 'end': a:endSelection, - \ 'buf_nr': bufnr('%'), - \ 'content': join(l:lines, "\n"), - \} - let l:out = [] - let l:err = [] - - let l:job = jobstart([&shell, &shellcmdflag, l:async_cmd], { - \ 'on_stdout': {job_id, data, event -> extend(l:out, data)}, - \ 'on_stderr': {job_id, data, event -> extend(l:err, data)}, - \ 'on_exit': {job_id, status, event -> s:Prettier_Job_Nvim_Exit(status, l:dict, l:out, l:err)}, - \ }) - call jobsend(l:job, l:lines) - call jobclose(l:job, 'stdin') -endfunction - -function! s:Prettier_Job_Nvim_Exit(status, info, out, err) abort - if a:status != 0 - echoerr join(a:err, "\n") - return - endif - if len(a:out) == 0 | return | endif - - let l:last = a:out[len(a:out) - 1] - let l:out = l:last ==? '' ? a:out[0:len(a:out) - 2] : a:out - if a:info.content == join(l:out, "\n") - " no change - return - endif - - call nvim_buf_set_lines(a:info.buf_nr, a:info.start, a:info.end, 0, l:out) -endfunction - -function! prettier#Autoformat(...) abort - let l:curPos = getpos('.') - let l:maxLineLookup = 50 - let l:maxTimeLookupMs = 500 - let l:pattern = '@format' - let l:search = @/ - let l:winview = winsaveview() - - " we need to move selection to the top before looking up to avoid - " scanning a very long file - call cursor(1, 1) - - " Search starting at the start of the document - if search(l:pattern, 'n', l:maxLineLookup, l:maxTimeLookupMs) > 0 - " autoformat async - call prettier#Prettier(1) - endif - - " Restore the selection and if greater then before it defaults to end - call cursor(l:curPos[1], l:curPos[2]) - - " Restore view - call winrestview(l:winview) - - " Restore search - let @/=l:search -endfunction - -function! s:Prettier_Exec_Sync(cmd, startSelection, endSelection) abort - let l:bufferLinesList = getbufline(bufnr('%'), a:startSelection, a:endSelection) - - " vim 7 does not have support for passing a list to system() - let l:bufferLines = v:version <= 800 ? join(l:bufferLinesList, "\n") : l:bufferLinesList - - let l:out = split(system(a:cmd, l:bufferLines), '\n') - - " check system exit code - if v:shell_error - call s:Prettier_Parse_Error(l:out) - return - endif - - if (s:Has_Content_Changed(l:out, a:startSelection, a:endSelection) == 0) - return - endif - - call s:Apply_Prettier_Format(l:out, a:startSelection, a:endSelection) -endfunction - -function! s:Prettier_Exec_Async(cmd, startSelection, endSelection) abort - let l:async_cmd = a:cmd - - if has('win32') || has('win64') - let l:async_cmd = 'cmd.exe /c ' . a:cmd - endif - - let l:bufferName = bufname('%') - - if s:prettier_job_running != 1 - let s:prettier_job_running = 1 - let l:job = job_start([&shell, &shellcmdflag, l:async_cmd], { - \ 'out_io': 'buffer', - \ 'err_cb': {channel, msg -> s:Prettier_Job_Error(msg)}, - \ 'close_cb': {channel -> s:Prettier_Job_Close(channel, a:startSelection, a:endSelection, l:bufferName)}}) - let l:stdin = job_getchannel(l:job) - call ch_sendraw(l:stdin, join(getbufline(bufnr(l:bufferName), a:startSelection,a:endSelection), "\n")) - call ch_close_in(l:stdin) - endif -endfunction - -function! s:Prettier_Job_Close(channel, startSelection, endSelection, bufferName) abort - let l:out = [] - let l:currentBufferName = bufname('%') - let l:isInsideAnotherBuffer = a:bufferName != l:currentBufferName ? 1 : 0 - - let l:buff = ch_getbufnr(a:channel, 'out') - let l:out = getbufline(l:buff, 2, '$') - execute 'bd!' . l:buff - - " nothing to update - if (s:Has_Content_Changed(l:out, a:startSelection, a:endSelection) == 0) - let s:prettier_job_running = 0 - redraw! - return - endif - - if len(l:out) - " This is required due to race condition when user quickly switch buffers while the async - " cli has not finished running, vim 8.0.1039 has introduced setbufline() which can be used - " to fix this issue in a cleaner way, however since we still need to support older vim versions - " we will apply a more generic solution - if (l:isInsideAnotherBuffer) - if (bufloaded(str2nr(a:bufferName))) - try - silent exec 'sp '. escape(bufname(bufnr(a:bufferName)), ' \') - call s:Prettier_Format_And_Save(l:out, a:startSelection, a:endSelection) - catch - echohl WarningMsg | echom 'Prettier: failed to parse buffer: ' . a:bufferName | echohl NONE - finally - " we should then hide this buffer again - if a:bufferName == bufname('%') - silent hide - endif - endtry - endif - else - call s:Prettier_Format_And_Save(l:out, a:startSelection, a:endSelection) - endif - - let s:prettier_job_running = 0 - endif -endfunction - -function! s:Prettier_Format_And_Save(lines, start, end) abort - call s:Apply_Prettier_Format(a:lines, a:start, a:end) - write -endfunction - -function! s:Prettier_Job_Error(msg) abort - call s:Prettier_Parse_Error(split(a:msg, '\n')) - let s:prettier_job_running = 0 -endfunction - -function! s:Handle_Parsing_Errors(out) abort - let l:errors = [] - - for l:line in a:out - " matches: - " file.ext: SyntaxError: Unexpected token (2:8)sd - " stdin: SyntaxError: Unexpected token (2:8) - " [error] file.ext: SyntaxError: Unexpected token (2:8) - let l:match = matchlist(l:line, '^.*: \(.*\) (\(\d\{1,}\):\(\d\{1,}\)*)') - if !empty(l:match) - call add(l:errors, { 'bufnr': bufnr('%'), - \ 'text': l:match[1], - \ 'lnum': l:match[2], - \ 'col': l:match[3] }) - endif - endfor + call prettier#utils#quickfix#close() - if len(l:errors) - let l:winnr = winnr() - call setqflist(l:errors, 'r') - botright copen - if !g:prettier#quickfix_auto_focus - " Return the cursor back to the main buffer. - exe l:winnr . 'wincmd w' + " we will be using portion formatting, so we need to send entire buffer to prettier + if l:partialFormatEnabled + let l:startSelection = 1 + let l:endSelection = line('$') endif - let s:prettier_quickfix_open = 1 - endif -endfunction - -function! s:Has_Content_Changed(content, startLine, endLine) abort - return getbufline(bufnr('%'), 1, line('$')) == s:Get_New_Buffer(a:content, a:startLine, a:endLine) ? 0 : 1 -endfunction - -function! s:Get_New_Buffer(lines, start, end) abort - return getbufline(bufnr('%'), 1, a:start - 1) + a:lines + getbufline(bufnr('%'), a:end + 1, '$') -endfunction - -function! s:Apply_Prettier_Format(lines, startSelection, endSelection) abort - " store view - let l:winview = winsaveview() - let l:newBuffer = s:Get_New_Buffer(a:lines, a:startSelection, a:endSelection) - - " we should not replace contents if the newBuffer is empty - if empty(l:newBuffer) - return - endif - - " delete all lines on the current buffer - silent! execute '%delete _' - - " replace all lines from the current buffer with output from prettier - let l:idx = 0 - for l:line in l:newBuffer - silent! call append(l:idx, l:line) - let l:idx += 1 - endfor - - " delete trailing newline introduced by the above append procedure - silent! execute '$delete _' - - " Restore view - call winrestview(l:winview) -endfunction - -" By default we will default to our internal -" configuration settings for prettier -function! s:Get_Prettier_Exec_Args(config) abort - " Allow params to be passed as json format - " convert bellow usage of globals to a get function o the params defaulting to global - let l:cmd = ' --print-width ' . - \ get(a:config, 'printWidth', g:prettier#config#print_width) . - \ ' --tab-width ' . - \ get(a:config, 'tabWidth', g:prettier#config#tab_width) . - \ ' --use-tabs ' . - \ get(a:config, 'useTabs', g:prettier#config#use_tabs) . - \ ' --semi ' . - \ get(a:config, 'semi', g:prettier#config#semi) . - \ ' --single-quote ' . - \ get(a:config, 'singleQuote', g:prettier#config#single_quote) . - \ ' --bracket-spacing ' . - \ get(a:config, 'bracketSpacing', g:prettier#config#bracket_spacing) . - \ ' --jsx-bracket-same-line ' . - \ get(a:config, 'jsxBracketSameLine', g:prettier#config#jsx_bracket_same_line) . - \ ' --arrow-parens ' . - \ get(a:config, 'arrowParens', g:prettier#config#arrow_parens) . - \ ' --trailing-comma ' . - \ get(a:config, 'trailingComma', g:prettier#config#trailing_comma) . - \ ' --parser ' . - \ get(a:config, 'parser', g:prettier#config#parser) . - \ ' --config-precedence ' . - \ get(a:config, 'configPrecedence', g:prettier#config#config_precedence) . - \ ' --prose-wrap ' . - \ get(a:config, 'proseWrap', g:prettier#config#prose_wrap) . - \ ' --html-whitespace-sensitivity ' . - \ get(a:config, 'htmlWhitespaceSensitivity', g:prettier#config#html_whitespace_sensitivity) . - \ ' --stdin-filepath "' . - \ simplify(expand('%:p')) . '"' . - \ ' --loglevel error '. - \ ' --stdin ' - return l:cmd -endfunction - -" By default we will search for the following -" => user defined prettier cli path from vim configuration file -" => locally installed prettier inside node_modules on any parent folder -" => globally installed prettier -" => vim-prettier prettier installation -" => if all fails suggest install -function! s:Get_Prettier_Exec() abort - let l:user_defined_exec_path = fnamemodify(g:prettier#exec_cmd_path, ':p') - if executable(l:user_defined_exec_path) - return l:user_defined_exec_path - endif - - let l:local_exec = s:Get_Prettier_Local_Exec() - if executable(l:local_exec) - return fnameescape(l:local_exec) - endif - let l:global_exec = s:Get_Prettier_Global_Exec() - if executable(l:global_exec) - return fnameescape(l:global_exec) - endif - - let l:plugin_exec = s:Get_Prettier_Plugin_Exec() - if executable(l:plugin_exec) - return fnameescape(l:plugin_exec) - endif - - return -1 -endfunction - -function! s:Get_Prettier_Local_Exec() abort - return s:Get_Exec(getcwd()) -endfunction - -function! s:Get_Prettier_Global_Exec() abort - return s:Get_Exec() -endfunction - -function! s:Get_Prettier_Plugin_Exec() abort - return s:Get_Exec(s:root_dir) -endfunction - -function! s:Get_Exec(...) abort - let l:rootDir = a:0 > 0 ? a:1 : 0 - let l:exec = -1 - - if isdirectory(l:rootDir) - let l:dir = s:Traverse_Dir_Search(l:rootDir) - if l:dir != -1 - let l:exec = s:Get_Path_To_Exec(l:dir) - endif + " format buffer + call prettier#job#runner#run(l:cmd, l:startSelection, l:endSelection, l:async) else - let l:exec = s:Get_Path_To_Exec() + call prettier#logging#error#log('EXECUTABLE_NOT_FOUND_ERROR') endif - - return l:exec -endfunction - -function! s:Get_Path_To_Exec(...) abort - let l:rootDir = a:0 > 0 ? a:1 : -1 - let l:dir = l:rootDir != -1 ? l:rootDir . '/.bin/' : '' - return l:dir . 'prettier' endfunction -function! s:Traverse_Dir_Search(rootDir) abort - let l:root = a:rootDir - let l:dir = 'node_modules' - - while 1 - let l:search_dir = l:root . '/' . l:dir - if isdirectory(l:search_dir) - return l:search_dir +" Set autoformat toggle based on whether config file was found. +function! prettier#IsConfigPresent(config_files) abort + for config_file in a:config_files + if filereadable(findfile(config_file, '.;')) + return 1 endif - - let l:parent = fnamemodify(l:root, ':h') - if l:parent == l:root - return -1 - endif - - let l:root = l:parent - endwhile -endfunction - -function! s:Prettier_Parse_Error(errors) abort - echohl WarningMsg | echom 'Prettier: failed to parse buffer.' | echohl NONE - if g:prettier#quickfix_enabled - call s:Handle_Parsing_Errors(a:errors) - endif -endfunction - -" If we can't find any prettier installing we then suggest where to get it from -function! s:Suggest_Install_Prettier() abort - echohl WarningMsg | echom 'Prettier: no prettier executable installation found.' | echohl NONE + endfor + return 0 endfunction diff --git a/autoload/prettier/bridge/parser.vim b/autoload/prettier/bridge/parser.vim new file mode 100644 index 0000000..51c4069 --- /dev/null +++ b/autoload/prettier/bridge/parser.vim @@ -0,0 +1,24 @@ +" TODO +" this function should just returns the parsed errors list instead +" of opening the quickfix +function! prettier#bridge#parser#onError(out, autoFocus) abort + let l:errors = [] + + for l:line in a:out + " matches: + " file.ext: SyntaxError: Unexpected token (2:8)sd + " stdin: SyntaxError: Unexpected token (2:8) + " [error] file.ext: SyntaxError: Unexpected token (2:8) + let l:match = matchlist(l:line, '^.*: \(.*\) (\(\d\{1,}\):\(\d\{1,}\)*)') + if !empty(l:match) + call add(l:errors, { 'bufnr': bufnr('%'), + \ 'text': l:match[1], + \ 'lnum': l:match[2], + \ 'col': l:match[3] }) + endif + endfor + + if len(l:errors) + call prettier#utils#quickfix#open(l:errors, a:autoFocus) + endif +endfunction diff --git a/autoload/prettier/job/async/neovim.vim b/autoload/prettier/job/async/neovim.vim new file mode 100644 index 0000000..28ecb8a --- /dev/null +++ b/autoload/prettier/job/async/neovim.vim @@ -0,0 +1,98 @@ +let s:prettier_job_running = 0 + +function! prettier#job#async#neovim#run(cmd, startSelection, endSelection) abort + if s:prettier_job_running == 1 + return + endif + let s:prettier_job_running = 1 + + let l:lines = getline(a:startSelection, a:endSelection) + let l:dict = { + \ 'start': a:startSelection - 1, + \ 'end': a:endSelection, + \ 'buf_nr': bufnr('%'), + \ 'content': l:lines, + \} + let l:out = [] + let l:err = [] + + let l:job = jobstart([&shell, &shellcmdflag, a:cmd], { + \ 'stdout_buffered': 1, + \ 'stderr_buffered': 1, + \ 'on_stdout': {job_id, data, event -> extend(l:out, data)}, + \ 'on_stderr': {job_id, data, event -> extend(l:err, data)}, + \ 'on_exit': {job_id, status, event -> s:onExit(status, l:dict, l:out, l:err)}, + \ }) + call jobsend(l:job, l:lines) + call jobclose(l:job, 'stdin') +endfunction + +" todo +" Lets refactor this onExit to work similar to our solution for vim8 +" at the moment an info json object is been passed with some cached data +" that is than used to do the formatting, its not following the same convetion +" as the vim8 one and is error prone +" +" we should: +" +" 1. make it similar to the vim8 approach +" 2. extract common functionality either above to the runner or to some other module +" +" to test this it rellies on using nvim and having the flag +" +" note that somehow we exectuing both async and sync on nvim when using the autoformat +function! s:onExit(status, info, out, err) abort + let l:currentBufferNumber = bufnr('%') + let l:isInsideAnotherBuffer = a:info.buf_nr != l:currentBufferNumber ? 1 : 0 + let l:last = a:out[len(a:out) - 1] + let l:out = l:last ==? '' ? a:out[0:len(a:out) - 2] : a:out + + " parsing errors + if a:status != 0 + call prettier#job#runner#onError(a:err) + let s:prettier_job_running = 0 + return + endif + + " we have no prettier output so lets exit + if len(a:out) == 0 | return | endif + + " nothing to update + if (prettier#utils#buffer#willUpdatedLinesChangeBuffer(l:out, a:info.start, a:info.end) == 0) + let s:prettier_job_running = 0 + return + endif + + " This is required due to race condition when user quickly switch buffers while the async + " cli has not finished running, vim 8.0.1039 has introduced setbufline() which can be used + " to fix this issue in a cleaner way, however since we still need to support older vim versions + " we will apply a more generic solution + if (l:isInsideAnotherBuffer) + " Do no try to format buffers that have been closed + if (bufloaded(a:info.buf_nr)) + try + silent exec 'sp '. escape(bufname(a:info.buf_nr), ' \') + call nvim_buf_set_lines(a:info.buf_nr, a:info.start, a:info.end, 0, l:out) + noautocmd write + catch + call prettier#logging#error#log('PARSING_ERROR') + finally + " we should then hide this buffer again + if a:info.buf_nr == bufnr('%') + silent hide + endif + endtry + endif + else + " TODO + " move this to the buffer util and let it abstract away the saving buffer + " from here + " + " TODO + " we should be auto saving in order to work similar to vim8 + call nvim_buf_set_lines(a:info.buf_nr, a:info.start, a:info.end, 0, l:out) + noautocmd write + endif + + let s:prettier_job_running = 0 +endfunction diff --git a/autoload/prettier/job/async/vim.vim b/autoload/prettier/job/async/vim.vim new file mode 100644 index 0000000..29774b9 --- /dev/null +++ b/autoload/prettier/job/async/vim.vim @@ -0,0 +1,69 @@ +let s:prettier_job_running = 0 + +function! prettier#job#async#vim#run(cmd, startSelection, endSelection) abort + if s:prettier_job_running == 1 + return + endif + let s:prettier_job_running = 1 + + let l:bufferName = bufname('%') + + let l:job = job_start([&shell, &shellcmdflag, a:cmd], { + \ 'out_io': 'buffer', + \ 'err_cb': {channel, msg -> s:onError(msg)}, + \ 'close_cb': {channel -> s:onClose(channel, a:startSelection, a:endSelection, l:bufferName)}}) + + let l:stdin = job_getchannel(l:job) + + call ch_sendraw(l:stdin, join(getbufline(bufnr(l:bufferName), a:startSelection, a:endSelection), "\n")) + call ch_close_in(l:stdin) +endfunction + +function! s:onError(msg) abort + call prettier#job#runner#onError(split(a:msg, '\n')) + let s:prettier_job_running = 0 +endfunction + +function! s:onClose(channel, startSelection, endSelection, bufferName) abort + let l:out = [] + let l:currentBufferName = bufname('%') + let l:isInsideAnotherBuffer = a:bufferName != l:currentBufferName ? 1 : 0 + + let l:buff = ch_getbufnr(a:channel, 'out') + let l:out = getbufline(l:buff, 2, '$') + execute 'bd!' . l:buff + + " we have no prettier output so lets exit + if len(l:out) == 0 | return | endif + + " nothing to update + if (prettier#utils#buffer#willUpdatedLinesChangeBuffer(l:out, a:startSelection, a:endSelection) == 0) + let s:prettier_job_running = 0 + redraw! + return + endif + + " This is required due to race condition when user quickly switch buffers while the async + " cli has not finished running, vim 8.0.1039 has introduced setbufline() which can be used + " to fix this issue in a cleaner way, however since we still need to support older vim versions + " we will apply a more generic solution + if (l:isInsideAnotherBuffer) + " Do no try to format buffers that have been closed + if (bufloaded(str2nr(a:bufferName))) + try + silent exec 'sp '. escape(bufname(bufnr(a:bufferName)), ' \') + call prettier#utils#buffer#replaceAndSave(l:out, a:startSelection, a:endSelection) + catch + call prettier#logging#error#log('PARSING_ERROR', a:bufferName) + finally + " we should then hide this buffer again + if a:bufferName == bufname('%') + silent hide + endif + endtry + endif + else + call prettier#utils#buffer#replaceAndSave(l:out, a:startSelection, a:endSelection) + endif + let s:prettier_job_running = 0 +endfunction diff --git a/autoload/prettier/job/runner.vim b/autoload/prettier/job/runner.vim new file mode 100644 index 0000000..60cb8a5 --- /dev/null +++ b/autoload/prettier/job/runner.vim @@ -0,0 +1,63 @@ +" TODO +" move the bellow vim checks to UTILS +let s:isLegacyVim = v:version < 800 +let s:isNeoVim = has('nvim') +let s:isAsyncVim = !s:isLegacyVim && exists('*job_start') + +function! prettier#job#runner#run(cmd, startSelection, endSelection, async) abort + if a:async && (s:isAsyncVim || s:isNeoVim) + call s:asyncFormat(a:cmd, a:startSelection, a:endSelection) + else + call s:format(a:cmd, a:startSelection, a:endSelection) + endif +endfunction + +function! prettier#job#runner#onError(errors) abort + call prettier#logging#error#log('PARSING_ERROR') + if g:prettier#quickfix_enabled + call prettier#bridge#parser#onError(a:errors, g:prettier#quickfix_auto_focus) + endif +endfunction + +function! s:asyncFormat(cmd, startSelection, endSelection) abort + if !s:isAsyncVim && !s:isNeoVim + call s:format(a:cmd, a:startSelection, a:endSelection) + endif + + " required for Windows support on async operations + let l:cmd = a:cmd + if has('win32') || has('win64') + let l:cmd = 'cmd.exe /c ' . a:cmd + endif + + if s:isAsyncVim + call prettier#job#async#vim#run(l:cmd, a:startSelection, a:endSelection) + else + call prettier#job#async#neovim#run(l:cmd, a:startSelection, a:endSelection) + endif +endfunction + +function! s:format(cmd, startSelection, endSelection) abort + let l:bufferLinesList = getbufline(bufnr('%'), a:startSelection, a:endSelection) + + " vim 7 does not have support for passing a list to system() + let l:bufferLines = s:isLegacyVim ? join(l:bufferLinesList, "\n") : l:bufferLinesList + + " TODO + " since we are using two different types for system, maybe we should move it to utils shims + let l:out = split(system(a:cmd, l:bufferLines), '\n') + + " check system exit code + if v:shell_error + call prettier#job#runner#onError(l:out) + return + endif + + " TODO + " doing 0 checks seems weird can we do this bellow differently ? + if (prettier#utils#buffer#willUpdatedLinesChangeBuffer(l:out, a:startSelection, a:endSelection) == 0) + return + endif + + call prettier#utils#buffer#replace(l:out, a:startSelection, a:endSelection) +endfunction diff --git a/autoload/prettier/logging/error.vim b/autoload/prettier/logging/error.vim new file mode 100644 index 0000000..48ad048 --- /dev/null +++ b/autoload/prettier/logging/error.vim @@ -0,0 +1,12 @@ +let s:PREFIX_MSG = 'Prettier: ' +let s:ERRORS = { + \ 'EXECUTABLE_NOT_FOUND_ERROR': 'no prettier executable installation found', + \ 'PARSING_ERROR': 'failed to parse buffer', + \ } +let s:DEFAULT_ERROR = get(s:, 'PARSING_ERROR') + +function! prettier#logging#error#log(...) abort + let l:error = a:0 > 0 ? a:1 : s:DEFAULT_ERROR + let l:msg = a:0 > 1 ? ': ' . a:2 : '' + echohl WarningMsg | echom s:PREFIX_MSG . get(s:ERRORS, l:error, s:DEFAULT_ERROR) . l:msg | echohl NONE +endfunction diff --git a/autoload/prettier/presets/fb.vim b/autoload/prettier/presets/fb.vim new file mode 100644 index 0000000..4715b88 --- /dev/null +++ b/autoload/prettier/presets/fb.vim @@ -0,0 +1,13 @@ +" Return facebook style config overwrite presets +function! prettier#presets#fb#config() abort + return { + \ 'bracketSpacing': 'false', + \ 'jsxBracketSameLine': 'true', + \ 'printWidth': 80, + \ 'parser': 'flow', + \ 'singleQuote': 'true', + \ 'tabWidth': 2, + \ 'trailingComma': 'all', + \ 'useTabs': 'false', + \ } +endfunction diff --git a/autoload/prettier/resolver/config.vim b/autoload/prettier/resolver/config.vim new file mode 100644 index 0000000..b22f11a --- /dev/null +++ b/autoload/prettier/resolver/config.vim @@ -0,0 +1,98 @@ +" By default we will default to our internal +" configuration settings for prettier +function! prettier#resolver#config#resolve(config, hasSelection, start, end) abort + " Allow params to be passed as json format + " convert bellow usage of globals to a get function o the params defaulting to global + " TODO: Use a list, filter() and join() to get a nicer list of args. + let l:cmd = s:Flag_use_tabs(a:config) . ' ' . + \ s:Flag_tab_width(a:config) . ' ' . + \ s:Flag_print_width(a:config) . ' ' . + \ s:Flag_parser(a:config) . ' ' . + \ s:Flag_range_delimiter(a:config, a:hasSelection, a:start, a:end) . ' ' . + \ ' --semi=' . + \ get(a:config, 'semi', g:prettier#config#semi) . + \ ' --single-quote=' . + \ get(a:config, 'singleQuote', g:prettier#config#single_quote) . + \ ' --bracket-spacing=' . + \ get(a:config, 'bracketSpacing', g:prettier#config#bracket_spacing) . + \ ' --jsx-bracket-same-line=' . + \ get(a:config, 'jsxBracketSameLine', g:prettier#config#jsx_bracket_same_line) . + \ ' --arrow-parens=' . + \ get(a:config, 'arrowParens', g:prettier#config#arrow_parens) . + \ ' --trailing-comma=' . + \ get(a:config, 'trailingComma', g:prettier#config#trailing_comma) . + \ ' --config-precedence=' . + \ get(a:config, 'configPrecedence', g:prettier#config#config_precedence) . + \ ' --prose-wrap=' . + \ get(a:config, 'proseWrap', g:prettier#config#prose_wrap) . + \ ' --html-whitespace-sensitivity ' . + \ get(a:config, 'htmlWhitespaceSensitivity', g:prettier#config#html_whitespace_sensitivity) . + \ ' --stdin-filepath="'.simplify(expand('%:p')).'"' . + \ ' --require-pragma=' . + \ get(a:config, 'requirePragma', g:prettier#config#require_pragma) . + \ ' --loglevel error '. + \ ' --stdin ' + return l:cmd +endfunction + +" Returns either '--range-start X --range-end Y' or an empty string. +function! s:Flag_range_delimiter(config, partialFormatEnabled, start, end) abort + if (!a:partialFormatEnabled) + return '' + endif + + let l:range = prettier#utils#buffer#getCharRange(a:start, a:end) + + return '--range-start=' . l:range[0] . ' --range-end=' . l:range[1] +endfunction + +" Returns '--tab-width=NN' +function! s:Flag_tab_width(config) abort + let l:value = get(a:config, 'tabWidth', g:prettier#config#tab_width) + + if (l:value ==# 'auto') + let l:value = prettier#utils#shim#shiftwidth() + endif + + return '--tab-width=' . l:value +endfunction + +" Returns either '--use-tabs' or an empty string. +function! s:Flag_use_tabs(config) abort + let l:value = get(a:config, 'useTabs', g:prettier#config#use_tabs) + if (l:value ==# 'auto') + let l:value = &expandtab ? 'false' : 'true' + endif + + if ( l:value ==# 'true' ) + return ' --use-tabs' + else + return '' + endif +endfunction + +" Returns '--print-width=NN' or '' +function! s:Flag_print_width(config) abort + let l:value = get(a:config, 'printWidth', g:prettier#config#print_width) + + if (l:value ==# 'auto') + let l:value = &textwidth + endif + + if (l:value > 0) + return '--print-width=' . l:value + else + return '' + endif +endfunction + +" Returns '--parser=PARSER' or '' +function! s:Flag_parser(config) abort + let l:value = get(a:config, 'parser', g:prettier#config#parser) + + if (l:value !=# '') + return '--parser=' . l:value + else + return '' + endif +endfunction diff --git a/autoload/prettier/resolver/executable.vim b/autoload/prettier/resolver/executable.vim new file mode 100644 index 0000000..8d233ab --- /dev/null +++ b/autoload/prettier/resolver/executable.vim @@ -0,0 +1,74 @@ +let s:ROOT_DIR = fnamemodify(resolve(expand('<sfile>:p')), ':h') + +" By default we will search for the following +" => user defined prettier cli path from vim configuration file +" => locally installed prettier inside node_modules on any parent folder +" => globally installed prettier +" => vim-prettier prettier installation +" => if all fails suggest install +function! prettier#resolver#executable#getPath() abort + let l:user_defined_exec_path = fnamemodify(g:prettier#exec_cmd_path, ':p') + if executable(l:user_defined_exec_path) + return l:user_defined_exec_path + endif + + let l:localExec = s:ResolveExecutable(getcwd()) + if executable(l:localExec) + return fnameescape(l:localExec) + endif + + let l:globalExec = s:ResolveExecutable() + if executable(l:globalExec) + return fnameescape(l:globalExec) + endif + + let l:pluginExec = s:ResolveExecutable(s:ROOT_DIR) + if executable(l:pluginExec) + return fnameescape(l:pluginExec) + endif + + return -1 +endfunction + +function! s:GetExecPath(...) abort + let l:rootDir = a:0 > 0 ? a:1 : -1 + let l:dir = l:rootDir != -1 ? l:rootDir . '/.bin/' : '' + return l:dir . 'prettier' +endfunction + +" Searches for the existence of a directory accross +" ancestral parents +function! s:TraverseAncestorDirSearch(rootDir) abort + let l:root = a:rootDir + let l:dir = 'node_modules' + + while 1 + let l:searchDir = l:root . '/' . l:dir + if isdirectory(l:searchDir) + return l:searchDir + endif + + let l:parent = fnamemodify(l:root, ':h') + if l:parent == l:root + return -1 + endif + + let l:root = l:parent + endwhile +endfunction + +function! s:ResolveExecutable(...) abort + let l:rootDir = a:0 > 0 ? a:1 : 0 + let l:exec = -1 + + if isdirectory(l:rootDir) + let l:dir = s:TraverseAncestorDirSearch(l:rootDir) + if l:dir != -1 + let l:exec = s:GetExecPath(l:dir) + endif + else + let l:exec = s:GetExecPath() + endif + + return l:exec +endfunction diff --git a/autoload/prettier/resolver/preset.vim b/autoload/prettier/resolver/preset.vim new file mode 100644 index 0000000..03f98ee --- /dev/null +++ b/autoload/prettier/resolver/preset.vim @@ -0,0 +1,8 @@ +" Build config using predefined preset +function! prettier#resolver#preset#resolve(fileTypeConfigOverwrites) abort + if ( g:prettier#preset#config ==# 'fb' ) + return extend(prettier#presets#fb#config(), a:fileTypeConfigOverwrites) + endif + + return a:fileTypeConfigOverwrites +endfunction diff --git a/autoload/prettier/utils/buffer.vim b/autoload/prettier/utils/buffer.vim new file mode 100644 index 0000000..7bc7ab1 --- /dev/null +++ b/autoload/prettier/utils/buffer.vim @@ -0,0 +1,70 @@ +function! prettier#utils#buffer#replace(lines, startSelection, endSelection) abort + " store view + let l:winview = winsaveview() + let l:newBuffer = prettier#utils#buffer#createBufferFromUpdatedLines(a:lines, a:startSelection, a:endSelection) + + " we should not replace contents if the newBuffer is empty + if empty(l:newBuffer) + return + endif + + " https://vim.fandom.com/wiki/Restore_the_cursor_position_after_undoing_text_change_made_by_a_script + " create a fake change entry and merge with undo stack prior to do formating + normal! ix + normal! x + try | silent undojoin | catch | endtry + + " delete all lines on the current buffer + silent! execute '%delete _' + + " replace all lines from the current buffer with output from prettier + let l:idx = 0 + for l:line in l:newBuffer + silent! call append(l:idx, l:line) + let l:idx += 1 + endfor + + " delete trailing newline introduced by the above append procedure + silent! execute '$delete _' + + " Restore view + call winrestview(l:winview) + +endfunction + +" Replace and save the buffer +function! prettier#utils#buffer#replaceAndSave(lines, startSelection, endSelection) abort + call prettier#utils#buffer#replace(a:lines, a:startSelection, a:endSelection) + noautocmd write +endfunction + +" Returns 1 if content has changed +function! prettier#utils#buffer#willUpdatedLinesChangeBuffer(lines, start, end) abort + return getbufline(bufnr('%'), 1, line('$')) == prettier#utils#buffer#createBufferFromUpdatedLines(a:lines, a:start, a:end) ? 0 : 1 +endfunction + +" Returns a new buffer with lines replacing start and end of the contents of the current buffer +function! prettier#utils#buffer#createBufferFromUpdatedLines(lines, start, end) abort + return getbufline(bufnr('%'), 1, a:start - 1) + a:lines + getbufline(bufnr('%'), a:end + 1, '$') +endfunction + +" Adapted from https://github.com/farazdagi/vim-go-ide +function! s:getCharPosition(line, col) abort + if &encoding !=# 'utf-8' + " On utf-8 enconding we can't just use bytes so we need to make sure we can count the + " characters, we do that by adding the text into a temporary buffer and counting the chars + let l:buf = a:line == 1 ? '' : (join(getline(1, a:line - 1), "\n") . "\n") + let l:buf .= a:col == 1 ? '' : getline('.')[:a:col - 2] + return len(iconv(l:buf, &encoding, 'utf-8')) + endif + " On non utf-8 the line byte should match the character + return line2byte(a:line) + (a:col - 2) +endfun + +" Returns [start, end] byte range when on visual mode +function! prettier#utils#buffer#getCharRange(startSelection, endSelection) abort + let l:range = [] + call add(l:range, s:getCharPosition(a:startSelection, col("'<"))) + call add(l:range, s:getCharPosition(a:endSelection, col("'>"))) + return l:range +endfunction diff --git a/autoload/prettier/utils/quickfix.vim b/autoload/prettier/utils/quickfix.vim new file mode 100644 index 0000000..b4766a7 --- /dev/null +++ b/autoload/prettier/utils/quickfix.vim @@ -0,0 +1,22 @@ +" We use this flag so that we ensure only clearing quickfix if it was created by prettier itself +let s:prettier_quickfix_open = 0 + +function! prettier#utils#quickfix#close() abort + " close quickfix if it is opened + if s:prettier_quickfix_open + call setqflist([], 'r') + cclose + let s:prettier_quickfix_open = 0 + endif +endfunction + +function! prettier#utils#quickfix#open(errors, focus) abort + let s:prettier_quickfix_open = 1 + let l:winnr = winnr() + call setqflist(a:errors, 'r') + botright copen + if !a:focus + " Return the cursor back to the main buffer. + exe l:winnr . 'wincmd w' + endif +endfunction diff --git a/autoload/prettier/utils/shim.vim b/autoload/prettier/utils/shim.vim new file mode 100644 index 0000000..228829e --- /dev/null +++ b/autoload/prettier/utils/shim.vim @@ -0,0 +1,8 @@ +" Backwards compatable version of shiftwidth() +function! prettier#utils#shim#shiftwidth() abort + if exists('*shiftwidth') + return shiftwidth() + else + return &shiftwidth + endif +endfunction |
