Change elm provider, closes #224

This commit is contained in:
Adam Stankiewicz
2017-09-27 20:52:13 +02:00
parent 6a12aa87f4
commit 7673a61990
10 changed files with 829 additions and 262 deletions

382
autoload/elm.vim Normal file
View File

@@ -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

View File

@@ -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

178
autoload/elm/util.vim Normal file
View File

@@ -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