aboutsummaryrefslogtreecommitdiff
path: root/autoload
diff options
context:
space:
mode:
Diffstat (limited to 'autoload')
-rw-r--r--autoload/prettier.vim427
-rw-r--r--autoload/prettier/bridge/parser.vim24
-rw-r--r--autoload/prettier/job/async/neovim.vim98
-rw-r--r--autoload/prettier/job/async/vim.vim69
-rw-r--r--autoload/prettier/job/runner.vim63
-rw-r--r--autoload/prettier/logging/error.vim12
-rw-r--r--autoload/prettier/presets/fb.vim13
-rw-r--r--autoload/prettier/resolver/config.vim98
-rw-r--r--autoload/prettier/resolver/executable.vim74
-rw-r--r--autoload/prettier/resolver/preset.vim8
-rw-r--r--autoload/prettier/utils/buffer.vim70
-rw-r--r--autoload/prettier/utils/quickfix.vim22
-rw-r--r--autoload/prettier/utils/shim.vim8
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