diff options
author | Adam Stankiewicz <sheerun@sher.pl> | 2017-09-27 20:52:13 +0200 |
---|---|---|
committer | Adam Stankiewicz <sheerun@sher.pl> | 2017-09-27 20:52:13 +0200 |
commit | 7673a61990d4062adebbe49f71067b0aad90382a (patch) | |
tree | 06f6a5e83257abb59930153e15e0644b504ac94b /autoload | |
parent | 6a12aa87f41b02a68cd8e6b494e5400367c2b028 (diff) | |
download | vim-polyglot-7673a61990d4062adebbe49f71067b0aad90382a.tar.gz vim-polyglot-7673a61990d4062adebbe49f71067b0aad90382a.zip |
Change elm provider, closes #224
Diffstat (limited to 'autoload')
-rw-r--r-- | autoload/elm.vim | 382 | ||||
-rw-r--r-- | autoload/elm/io.vim | 12 | ||||
-rw-r--r-- | autoload/elm/util.vim | 178 |
3 files changed, 560 insertions, 12 deletions
diff --git a/autoload/elm.vim b/autoload/elm.vim new file mode 100644 index 00000000..85a1a0b2 --- /dev/null +++ b/autoload/elm.vim @@ -0,0 +1,382 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'elm') == -1 + +let s:errors = [] + +function! s:elmOracle(...) abort + let l:project = finddir('elm-stuff/..', '.;') + if len(l:project) == 0 + echoerr '`elm-stuff` not found! run `elm-package install` for autocomplete.' + return [] + endif + + let l:filename = expand('%:p') + + if a:0 == 0 + let l:oldiskeyword = &iskeyword + " Some non obvious values used in 'iskeyword': + " @ = all alpha + " 48-57 = numbers 0 to 9 + " @-@ = character @ + " 124 = | + setlocal iskeyword=@,48-57,@-@,_,-,~,!,#,$,%,&,*,+,=,<,>,/,?,.,\\,124,^ + let l:word = expand('<cword>') + let &iskeyword = l:oldiskeyword + else + let l:word = a:1 + endif + + let l:infos = elm#Oracle(l:filename, l:word) + if v:shell_error != 0 + call elm#util#EchoError("elm-oracle failed:\n\n", l:infos) + return [] + endif + + let l:d = split(l:infos, '\n') + if len(l:d) > 0 + return elm#util#DecodeJSON(l:d[0]) + endif + + return [] +endf + +" Vim command to format Elm files with elm-format +function! elm#Format() abort + " check for elm-format + if elm#util#CheckBin('elm-format', 'https://github.com/avh4/elm-format') ==# '' + return + endif + + " save cursor position, folds and many other things + let l:curw = {} + try + mkview! + catch + let l:curw = winsaveview() + endtry + + " save our undo file to be restored after we are done. + let l:tmpundofile = tempname() + exe 'wundo! ' . l:tmpundofile + + " write current unsaved buffer to a temporary file + let l:tmpname = tempname() . '.elm' + call writefile(getline(1, '$'), l:tmpname) + + " call elm-format on the temporary file + let l:out = system('elm-format ' . l:tmpname . ' --output ' . l:tmpname) + + " if there is no error + if v:shell_error == 0 + try | silent undojoin | catch | endtry + + " replace current file with temp file, then reload buffer + let l:old_fileformat = &fileformat + call rename(l:tmpname, expand('%')) + silent edit! + let &fileformat = l:old_fileformat + let &syntax = &syntax + elseif g:elm_format_fail_silently == 0 + call elm#util#EchoLater('EchoError', 'elm-format:', l:out) + endif + + " save our undo history + silent! exe 'rundo ' . l:tmpundofile + call delete(l:tmpundofile) + + " restore our cursor/windows positions, folds, etc.. + if empty(l:curw) + silent! loadview + else + call winrestview(l:curw) + endif +endf + +" Query elm-oracle and echo the type and docs for the word under the cursor. +function! elm#ShowDocs() abort + " check for the elm-oracle binary + if elm#util#CheckBin('elm-oracle', 'https://github.com/elmcast/elm-oracle') ==# '' + return + endif + + let l:response = s:elmOracle() + + if len(l:response) > 0 + let l:info = l:response[0] + redraws! | echohl Identifier | echon l:info.fullName | echohl None | echon ' : ' | echohl Function | echon l:info.signature | echohl None | echon "\n\n" . l:info.comment + else + call elm#util#Echo('elm-oracle:', '...no match found') + endif +endf + +" Query elm-oracle and open the docs for the word under the cursor. +function! elm#BrowseDocs() abort + " check for the elm-oracle binary + if elm#util#CheckBin('elm-oracle', 'https://github.com/elmcast/elm-oracle') ==# '' + return + endif + + let l:response = s:elmOracle() + + if len(l:response) > 0 + let l:info = l:response[0] + call elm#util#OpenBrowser(l:info.href) + else + call elm#util#Echo('elm-oracle:', '...no match found') + endif +endf + + +function! elm#Syntastic(input) abort + let l:fixes = [] + + let l:bin = 'elm-make' + let l:format = '--report=json' + let l:input = shellescape(a:input) + let l:output = '--output=' . shellescape(syntastic#util#DevNull()) + let l:command = l:bin . ' ' . l:format . ' ' . l:input . ' ' . l:output + let l:reports = s:ExecuteInRoot(l:command) + + for l:report in split(l:reports, '\n') + if l:report[0] ==# '[' + for l:error in elm#util#DecodeJSON(l:report) + if g:elm_syntastic_show_warnings == 0 && l:error.type ==? 'warning' + else + if a:input == l:error.file + call add(s:errors, l:error) + call add(l:fixes, {'filename': l:error.file, + \'valid': 1, + \'bufnr': bufnr('%'), + \'type': (l:error.type ==? 'error') ? 'E' : 'W', + \'lnum': l:error.region.start.line, + \'col': l:error.region.start.column, + \'text': l:error.overview}) + endif + endif + endfor + endif + endfor + + return l:fixes +endf + +function! elm#Build(input, output, show_warnings) abort + let s:errors = [] + let l:fixes = [] + let l:rawlines = [] + + let l:bin = 'elm-make' + let l:format = '--report=json' + let l:input = shellescape(a:input) + let l:output = '--output=' . shellescape(a:output) + let l:command = l:bin . ' ' . l:format . ' ' . l:input . ' ' . l:output + let l:reports = s:ExecuteInRoot(l:command) + + for l:report in split(l:reports, '\n') + if l:report[0] ==# '[' + for l:error in elm#util#DecodeJSON(l:report) + if a:show_warnings == 0 && l:error.type ==? 'warning' + else + call add(s:errors, l:error) + call add(l:fixes, {'filename': l:error.file, + \'valid': 1, + \'type': (l:error.type ==? 'error') ? 'E' : 'W', + \'lnum': l:error.region.start.line, + \'col': l:error.region.start.column, + \'text': l:error.overview}) + endif + endfor + else + call add(l:rawlines, l:report) + endif + endfor + + let l:details = join(l:rawlines, "\n") + let l:lines = split(l:details, "\n") + if !empty(l:lines) + let l:overview = l:lines[0] + else + let l:overview = '' + endif + + if l:details ==# '' || l:details =~? '^Successfully.*' + else + call add(s:errors, {'overview': l:details, 'details': l:details}) + call add(l:fixes, {'filename': expand('%', 1), + \'valid': 1, + \'type': 'E', + \'lnum': 0, + \'col': 0, + \'text': l:overview}) + endif + + return l:fixes +endf + +" Make the given file, or the current file if none is given. +function! elm#Make(...) abort + if elm#util#CheckBin('elm-make', 'http://elm-lang.org/install') ==# '' + return + endif + + call elm#util#Echo('elm-make:', 'building...') + + let l:input = (a:0 == 0) ? expand('%:p') : a:1 + let l:fixes = elm#Build(l:input, g:elm_make_output_file, g:elm_make_show_warnings) + + if len(l:fixes) > 0 + call elm#util#EchoWarning('', 'found ' . len(l:fixes) . ' errors') + + call setqflist(l:fixes, 'r') + cwindow + + if get(g:, 'elm_jump_to_error', 1) + ll 1 + endif + else + call elm#util#EchoSuccess('', 'Sucessfully compiled') + + call setqflist([]) + cwindow + endif +endf + +" Show the detail of the current error in the quickfix window. +function! elm#ErrorDetail() abort + if !empty(filter(tabpagebuflist(), 'getbufvar(v:val, "&buftype") ==? "quickfix"')) + exec ':copen' + let l:linenr = line('.') + exec ':wincmd p' + if len(s:errors) > 0 + let l:detail = s:errors[l:linenr-1].details + if l:detail ==# '' + let l:detail = s:errors[l:linenr-1].overview + endif + echo l:detail + endif + endif +endf + +" Open the elm repl in a subprocess. +function! elm#Repl() abort + " check for the elm-repl binary + if elm#util#CheckBin('elm-repl', 'http://elm-lang.org/install') ==# '' + return + endif + + if has('nvim') + term('elm-repl') + else + !elm-repl + endif +endf + +function! elm#Oracle(filepath, word) abort + let l:bin = 'elm-oracle' + let l:filepath = shellescape(a:filepath) + let l:word = shellescape(a:word) + let l:command = l:bin . ' ' . l:filepath . ' ' . l:word + return s:ExecuteInRoot(l:command) +endfunction + +let s:fullComplete = '' + +" Complete the current token using elm-oracle +function! elm#Complete(findstart, base) abort +" a:base is unused, but the callback function for completion expects 2 arguments + if a:findstart + let l:line = getline('.') + + let l:idx = col('.') - 1 + let l:start = 0 + while l:idx > 0 && l:line[l:idx - 1] =~# '[a-zA-Z0-9_\.]' + if l:line[l:idx - 1] ==# '.' && l:start == 0 + let l:start = l:idx + endif + let l:idx -= 1 + endwhile + + if l:start == 0 + let l:start = l:idx + endif + + let s:fullComplete = l:line[l:idx : col('.')-2] + + return l:start + else + " check for the elm-oracle binary + if elm#util#CheckBin('elm-oracle', 'https://github.com/elmcast/elm-oracle') ==# '' + return [] + endif + + let l:res = [] + let l:response = s:elmOracle(s:fullComplete) + + let l:detailed = get(g:, 'elm_detailed_complete', 0) + + for l:r in l:response + let l:menu = '' + if l:detailed + let l:menu = ': ' . l:r.signature + endif + call add(l:res, {'word': l:r.name, 'menu': l:menu}) + endfor + + return l:res + endif +endf + +" If the current buffer contains a consoleRunner, run elm-test with it. +" Otherwise run elm-test in the root of your project which deafults to +" running 'elm-test tests/TestRunner'. +function! elm#Test() abort + if elm#util#CheckBin('elm-test', 'https://github.com/rtfeldman/node-elm-test') ==# '' + return + endif + + if match(getline(1, '$'), 'consoleRunner') < 0 + let l:out = s:ExecuteInRoot('elm-test') + call elm#util#EchoSuccess('elm-test', l:out) + else + let l:filepath = shellescape(expand('%:p')) + let l:out = s:ExecuteInRoot('elm-test ' . l:filepath) + call elm#util#EchoSuccess('elm-test', l:out) + endif +endf + +" Returns the closest parent with an elm-package.json file. +function! elm#FindRootDirectory() abort + let l:elm_root = getbufvar('%', 'elmRoot') + if empty(l:elm_root) + let l:current_file = expand('%:p') + let l:dir_current_file = fnameescape(fnamemodify(l:current_file, ':h')) + let l:match = findfile('elm-package.json', l:dir_current_file . ';') + if empty(l:match) + let l:elm_root = '' + else + let l:elm_root = fnamemodify(l:match, ':p:h') + endif + + if !empty(l:elm_root) + call setbufvar('%', 'elmRoot', l:elm_root) + endif + endif + return l:elm_root +endfunction + +" Executes a command in the project directory. +function! s:ExecuteInRoot(cmd) abort + let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd ' : 'cd ' + let l:current_dir = getcwd() + let l:root_dir = elm#FindRootDirectory() + + try + execute l:cd . fnameescape(l:root_dir) + let l:out = system(a:cmd) + finally + execute l:cd . fnameescape(l:current_dir) + endtry + + return l:out +endfunction + +endif diff --git a/autoload/elm/io.vim b/autoload/elm/io.vim deleted file mode 100644 index 509a8f6a..00000000 --- a/autoload/elm/io.vim +++ /dev/null @@ -1,12 +0,0 @@ -if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'elm') == -1 - -" System IO - -" Craft a system command and run it, returning the output. -function! elm#io#system(program, args) - let cmd ="which " . a:program . " && " . a:program . " " . a:args - return system(cmd) -endfunction - - -endif diff --git a/autoload/elm/util.vim b/autoload/elm/util.vim new file mode 100644 index 00000000..b276394c --- /dev/null +++ b/autoload/elm/util.vim @@ -0,0 +1,178 @@ +if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'elm') == -1 + +" IsWin returns 1 if current OS is Windows or 0 otherwise +fun! elm#util#IsWin() abort + let l:win = ['win16', 'win32', 'win32unix', 'win64', 'win95'] + for l:w in l:win + if (has(l:w)) + return 1 + endif + endfor + + return 0 +endf + +fun! elm#util#CheckBin(bin, url) abort + let l:binpath = substitute(a:bin, '^\s*\(.\{-}\)\s*$', '\1', '') + + if executable(l:binpath) + return l:binpath + endif + + call elm#util#EchoWarning('elm-vim:', 'could not find ' . l:binpath . ' [' . a:url . ']') + + return '' +endf + +" Determines the browser command to use +fun! s:get_browser_command() abort + let l:elm_browser_command = get(g:, 'elm_browser_command', '') + if l:elm_browser_command ==? '' + if elm#util#IsWin() + let l:elm_browser_command = '!start rundll32 url.dll,FileProtocolHandler %URL%' + elseif has('mac') || has('macunix') || has('gui_macvim') || system('uname') =~? '^darwin' + let l:elm_browser_command = 'open %URL%' + elseif executable('xdg-open') + let l:elm_browser_command = 'xdg-open %URL%' + elseif executable('firefox') + let l:elm_browser_command = 'firefox %URL% &' + else + let l:elm_browser_command = '' + endif + endif + return l:elm_browser_command +endf + +" OpenBrowser opens a url in the default browser +fun! elm#util#OpenBrowser(url) abort + let l:cmd = s:get_browser_command() + if len(l:cmd) == 0 + redraw + echohl WarningMsg + echo "It seems that you don't have general web browser. Open URL below." + echohl None + echo a:url + return + endif + if l:cmd =~? '^!' + let l:cmd = substitute(l:cmd, '%URL%', '\=shellescape(a:url)', 'g') + silent! exec l:cmd + elseif l:cmd =~# '^:[A-Z]' + let l:cmd = substitute(l:cmd, '%URL%', '\=a:url', 'g') + exec l:cmd + else + let l:cmd = substitute(l:cmd, '%URL%', '\=shellescape(a:url)', 'g') + call system(l:cmd) + endif +endf + +" DecodeJSON decodes a string of json into a viml object +fun! elm#util#DecodeJSON(s) abort + let l:true = 1 + let l:false = 0 + let l:null = 0 + return eval(a:s) +endf + +" Remove ANSI escape characters used for highlighting purposes +fun! s:strip_color(msg) abort + return substitute(a:msg, '\e\[[0-9;]\+[mK]', '', 'g') +endf + +" Print functions +fun! elm#util#Echo(title, msg) abort + redraws! | echon a:title . ' ' | echohl Identifier | echon s:strip_color(a:msg) | echohl None +endf + +fun! elm#util#EchoSuccess(title, msg) abort + redraws! | echon a:title . ' ' | echohl Function | echon s:strip_color(a:msg) | echohl None +endf + +fun! elm#util#EchoWarning(title, msg) abort + redraws! | echon a:title . ' ' | echohl WarningMsg | echon s:strip_color(a:msg) | echohl None +endf + +fun! elm#util#EchoError(title, msg) abort + redraws! | echon a:title . ' ' | echohl ErrorMsg | echon s:strip_color(a:msg) | echohl None +endf + +fun! elm#util#EchoLater(func_name, title, msg) abort + let s:echo_func_name = a:func_name + let s:echo_title = a:title + let s:echo_msg = a:msg +endf + +fun! elm#util#EchoStored() abort + if exists('s:echo_func_name') && exists('s:echo_title') && exists('s:echo_msg') + call elm#util#{s:echo_func_name}(s:echo_title, s:echo_msg) + unlet s:echo_func_name + unlet s:echo_title + unlet s:echo_msg + endif +endf + +function! elm#util#GoToModule(name) + if empty(a:name) | return | endif + if empty(matchstr(a:name, '^Native\.')) + let l:extension = '.elm' + else + let l:extension = '.js' + endif + let l:rel_path = substitute(a:name, '\.', '/', 'g') . l:extension + let l:root = elm#FindRootDirectory() + + let l:module_file = s:findLocalModule(l:rel_path, l:root) + if !filereadable(l:module_file) + let l:module_file = s:findDependencyModule(l:rel_path, l:root) + endif + + if filereadable(l:module_file) + exec 'edit ' . fnameescape(l:module_file) + else + return s:error("Can't find module \"" . a:name . "\"") + endif +endfunction + +function! s:findLocalModule(rel_path, root) + let l:package_json = a:root . '/elm-package.json' + if exists('*json_decode') + let l:package = json_decode(readfile(l:package_json)) + let l:source_roots = l:package['source-directories'] + else + " This is a fallback for vim's which do not support json_decode. + " It simply only looks in the 'src' subdirectory and fails otherwise. + let l:source_roots = ['src'] + end + for l:source_root in l:source_roots + let l:file_path = a:root . '/' . l:source_root . '/' . a:rel_path + if !filereadable(l:file_path) + continue + endif + return l:file_path + endfor +endfunction + +function! s:findDependencyModule(rel_path, root) + " If we are a dependency ourselves, we need to check our siblings. + " This is because elm package doesn't install dependencies recursively. + let l:root = substitute(a:root, '\/elm-stuff/packages.\+$', '', '') + + " We naively craws the dependencies dir for any fitting module name. + " If it exists, we'll find it. If multiple filenames match, + " there's a chance we return the wrong one. + let l:module_paths = glob(l:root . '/elm-stuff/packages/**/' . a:rel_path, 0, 1) + if len(l:module_paths) > 0 + return l:module_paths[0] + endif +endfunction + +" Using the built-in :echoerr prints a stacktrace, which isn't that nice. +" From: https://github.com/moll/vim-node/blob/master/autoload/node.vim +function! s:error(msg) + echohl ErrorMsg + echomsg a:msg + echohl NONE + let v:errmsg = a:msg +endfunction + +endif |