├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── autoload ├── necoghc.vim ├── necoghc │ └── diagnostics.vim ├── neocomplcache │ └── sources │ │ └── ghc.vim └── neocomplete │ └── sources │ └── ghc.vim ├── neco-ghc.vimup ├── plugin └── necoghc.vim └── rplugin └── python3 └── deoplete └── sources └── ghc.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | **/tags 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Issues 4 | When submitting an issue, please include the output by `:NecoGhcDiagnostics`. 5 | 6 | ```vim 7 | NecoGhcDiagnostics 8 | ``` 9 | 10 | It shows your environment information possibly related to neco-ghc. 11 | 12 | - Current filetype 13 | - neco-ghc only works in the buffer with filetype haskell or lhaskell. 14 | - ghc-mod OR hhpc executable (required) 15 | - neco-ghc requires [ghc-mod](https://github.com/kazu-yamamoto/ghc-mod) or [hhp](https://github.com/kazu-yamamoto/hhp) and they must be placed in your `$PATH`. 16 | - 'omnifunc' 17 | - To use Vim builtin completion, `'omnifunc'` must be set to `necoghc#omnifunc` by `setlocal omnifunc=necoghc#omnifunc`. 18 | - Completion plugin installation (optional) 19 | - [neocomplete.vim](https://github.com/Shougo/neocomplete.vim) 20 | - [neocomplcache.vim](https://github.com/Shougo/neocomplcache.vim) 21 | - [deoplete.nvim](https://github.com/Shougo/deoplete.nvim) 22 | - [YouCompleteMe](https://github.com/Valloric/YouCompleteMe) 23 | - Other plugin installation (optional) 24 | - [vimproc.vim](https://github.com/Shougo/vimproc.vim) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # neco-ghc: ghc-mod/hhpc completion for neocomplcache/neocomplete/deoplete 2 | 3 | A completion plugin for Haskell, using ghc-mod or hhpc 4 | 5 | ## What is neco-ghc 6 | 7 | This plugin supports the following completion. 8 | 9 | * pragma 10 | ![](http://cache.gyazo.com/c922e323be7dbed9aa70b2bac62be45e.png) 11 | * language 12 | ![](http://cache.gyazo.com/9df4aa3cf06fc07495d6dd67a4d07cc4.png) 13 | * importing a module 14 | ![](http://cache.gyazo.com/17a8bf08f3a6d5e123346f5f1c74c5f9.png) 15 | * importing a function of a module 16 | ![](http://cache.gyazo.com/d3698892a40ffb8e4bef970a02198715.png) 17 | * function based on importing modules 18 | ![](http://cache.gyazo.com/bc168a8aad5f38c6a83b8aa1b0fb14f6.png) 19 | 20 | neco-ghc was originally implemented by @eagletmt on July 25, 2010, and then 21 | ujihisa added some new features. 22 | 23 | ## Install 24 | 25 | * Install the ghc-mod package by `stack install ghc-mod` or `cabal install 26 | ghc-mod` OR install the hhp package by `stack install hhp` or 27 | `cabal install hhp` 28 | * Unarchive neco-ghc and put it into a dir of your &rtp. 29 | 30 | Note: If you use ghc-mod 5.4, you should use ghc-mod 5.5+. 31 | Because, ghc-mod 5.5 fixes the rootdir problem. 32 | 33 | https://github.com/DanielG/ghc-mod/issues/665 34 | 35 | ## Usage 36 | 37 | neco-ghc provides `necoghc#omnifunc` for omni-completion. 38 | I recommend adding the following in your ~/.vim/ftplugin/haskell.vim. 39 | 40 | ```vim 41 | " Disable haskell-vim omnifunc 42 | let g:haskellmode_completion_ghc = 0 43 | autocmd FileType haskell setlocal omnifunc=necoghc#omnifunc 44 | ``` 45 | 46 | See `:help compl-omni` for details on omni-completion. 47 | 48 | ### Completion engines 49 | This plugin can be used as a source of 50 | [neocomplete.vim](https://github.com/Shougo/neocomplete.vim) or 51 | [neocomplcache.vim](https://github.com/Shougo/neocomplcache.vim) or 52 | [deoplete.nvim](https://github.com/Shougo/deoplete.nvim). 53 | You can enjoy auto-completions without any specific configuration. 54 | 55 | This plugin also should work with [YouCompleteMe](https://github.com/Valloric/YouCompleteMe). 56 | To enable auto-completions, you have to add the following setting. 57 | 58 | ```vim 59 | let g:ycm_semantic_triggers = {'haskell' : ['.']} 60 | ``` 61 | 62 | ## Options 63 | ### `g:necoghc_enable_detailed_browse` 64 | Default: 0 65 | 66 | Show detailed information (type) of symbols. 67 | You can enable it by adding `let g:necoghc_enable_detailed_browse = 1` in your vimrc. 68 | While it is quite useful, it will take longer boot time. 69 | 70 | This feature was introduced in ghc-mod 1.11.5. 71 | 72 | ![](http://cache.gyazo.com/f3d2c097475021615581822eee8cb6fd.png) 73 | 74 | ### `g:necoghc_debug` 75 | Default: 0 76 | 77 | Show error message if ghc-mod/hhpc command fails. 78 | Usually it will be noisy if `ghc-mod browse Your.Project.Module` always 79 | fails. 80 | Use this flag only if you have some trouble. 81 | 82 | ### `g:necoghc_use_stack` 83 | Default: 0 84 | 85 | Allow using stack's own ghc-mod/hhpc. 86 | If you are using ghc-mod, it will change direct `ghc-mod` mod calls to `stack 87 | exec --no-stack-exe ghc-mod --` instead. The same goes for hhpc. 88 | Use this flag if your globally installed ghc-mod/hhpc doesn't work properly with your 89 | stack projects. 90 | 91 | ## Troubleshoot 92 | 93 | ### Q: neco-ghc does not work 94 | 95 | Check the $PATH variable in vim contains the path to your ghc-mod/hhpc command. 96 | Or you can execute `:NecoGhcDiagnostics` command for debug. 97 | 98 | ### Q: Completion isn't working for local functions or modules 99 | 100 | https://github.com/eagletmt/neco-ghc/issues/44 101 | 102 | It's a limitation of ghc-mod. 103 | ghc-mod can show symbols of installed modules only. 104 | ghc-mod cannot show symbols of developing modules or current source file. 105 | 106 | ## License 107 | 108 | [BSD3 License](http://www.opensource.org/licenses/BSD-3-Clause), the same license as ghc-mod. 109 | -------------------------------------------------------------------------------- /autoload/necoghc.vim: -------------------------------------------------------------------------------- 1 | " http://www.haskell.org/ghc/docs/latest/html/users_guide/pragmas.html 2 | let s:pragmas = [ 3 | \ 'ANN', 4 | \ 'DEPRECATED', 5 | \ 'INCLUDE', 6 | \ 'INLINE', 7 | \ 'INLINABLE', 8 | \ 'LANGUAGE', 9 | \ 'LINE', 10 | \ 'MINIMAL', 11 | \ 'NOINLINE', 12 | \ 'NOUNPACK', 13 | \ 'OPTIONS_GHC', 14 | \ 'RULES', 15 | \ 'SOURCE', 16 | \ 'SPECIALIZE', 17 | \ 'UNPACK', 18 | \ 'WARNING', 19 | \ ] 20 | 21 | " todo: channel support 22 | let s:is_async = has('nvim') 23 | " let s:is_async = has('nvim') || (has('job') && has('channel') 24 | " \ && exists('*job_getchannel') 25 | " \ && exists('*job_info')) 26 | let s:job_info = {} 27 | let s:max_processes = 5 28 | 29 | if executable('hhpc') 30 | let g:necoghc#executable = 'hhpc' 31 | elseif executable('ghc-mod') 32 | let g:necoghc#executable = 'ghc-mod' 33 | else 34 | let g:necoghc#executable = '' 35 | endif 36 | 37 | let s:exe_path = [g:necoghc#executable] 38 | 39 | function! necoghc#boot() abort "{{{ 40 | if exists('s:browse_cache') 41 | return 42 | endif 43 | 44 | if get(g:, 'necoghc_use_stack', 0) 45 | let s:exe_path = ['stack', 'exec', '--no-stack-exe', g:necoghc#executable, '--'] 46 | endif 47 | 48 | let l:opts = get(g:, 'ghcmod_ghc_options', []) 49 | 50 | for l:opt in l:opts 51 | call extend(s:exe_path, ['-g', l:opt]) 52 | endfor 53 | 54 | let s:browse_cache = {} 55 | call s:ghc_mod_caching_browse('Prelude') 56 | 57 | augroup necoghc 58 | autocmd! 59 | autocmd FileType haskell call s:on_haskell() 60 | autocmd FileType lhaskell call s:on_haskell() 61 | augroup END 62 | 63 | call s:on_haskell() 64 | endfunction "}}} 65 | 66 | function! necoghc#omnifunc(findstart, base) abort "{{{ 67 | if a:findstart 68 | let l:col = col('.')-1 69 | if l:col == 0 70 | return -1 71 | else 72 | return necoghc#get_keyword_pos(getline('.')[0 : l:col-1]) 73 | endif 74 | else 75 | call necoghc#boot() 76 | " Redo get_keyword_pos to detect YouCompleteMe. 77 | let l:col = col('.')-1 78 | let l:pos = necoghc#get_keyword_pos(getline('.')[0 : l:col-1]) 79 | return necoghc#get_complete_words(l:pos, a:base) 80 | endif 81 | endfunction "}}} 82 | 83 | function! necoghc#get_keyword_pos(cur_text) abort "{{{ 84 | if s:synname() =~# 'Comment' 85 | return -1 86 | endif 87 | 88 | let [nothing, just_pos] = s:multiline_import(a:cur_text, 'pos') 89 | if !nothing 90 | return just_pos 91 | endif 92 | if a:cur_text =~# '^import\>' 93 | if a:cur_text =~# '(.*,' 94 | return matchend(a:cur_text, '^.*,\s*') 95 | endif 96 | let parp = matchend(a:cur_text, '(\s*') 97 | return parp > 0 ? parp : 98 | \ matchend(a:cur_text, '^import\s\+\(qualified\s\+\)\?') 99 | else 100 | if s:synname() =~# 'Pragma' && a:cur_text =~# 'OPTIONS_GHC' 101 | let l:pattern = '-[[:alnum:]-]*$' 102 | else 103 | let l:pattern = '\%([[:alpha:]_''][[:alnum:]_''.]*\m\)$' 104 | endif 105 | let l:pos = match(a:cur_text, l:pattern) 106 | if l:pos == -1 107 | " When the completion method is Vim (or YouCompleteMe?), a:cur_text is 108 | " '{-# '. 109 | let l:pos = strlen(a:cur_text) 110 | endif 111 | return l:pos 112 | endif 113 | endfunction "}}} 114 | 115 | function! s:word_prefix(dict, keyword, need_prefix_filter) abort "{{{ 116 | let l:len = strlen(a:keyword) 117 | if strpart(a:dict.word, 0, l:len) ==# a:keyword 118 | if a:need_prefix_filter 119 | let a:dict.word = strpart(a:dict.word, l:len) 120 | endif 121 | return 1 122 | else 123 | return 0 124 | endif 125 | endfunction "}}} 126 | 127 | function! s:to_desc(sym, dict) abort 128 | let l:desc = '' 129 | if has_key(a:dict, 'kind') 130 | let l:desc .= printf('%s %s %s', a:dict.kind, a:sym, a:dict.args) 131 | elseif has_key(a:dict, 'type') 132 | let l:desc .= printf('%s :: %s', a:sym, a:dict.type) 133 | else 134 | let l:desc .= a:sym 135 | endif 136 | return l:desc 137 | endfunction 138 | 139 | function! necoghc#get_complete_words(cur_keyword_pos, cur_keyword_str) abort "{{{ 140 | let l:col = col('.')-1 141 | " HACK: When invoked from Vim, col('.') returns the position returned by the 142 | " omnifunc in findstart phase. 143 | if a:cur_keyword_pos == l:col 144 | " Invoked from Vim. 145 | let l:cur_keyword_str = a:cur_keyword_str 146 | let l:need_prefix_filter = 0 147 | let l:need_filter = 1 148 | elseif empty(a:cur_keyword_str) 149 | " Invoked from YouCompleteMe. 150 | " It doesn't give correct a:base and doesn't filter out prefix. 151 | let l:cur_keyword_str = getline('.')[a:cur_keyword_pos : l:col-1] 152 | let l:need_prefix_filter = 1 153 | let l:need_filter = 1 154 | else 155 | " Invoked from neocomplcache.vim or neocomplete.vim. 156 | " They give correct a:base and doesn't need filter. 157 | let l:cur_keyword_str = a:cur_keyword_str 158 | let l:need_prefix_filter = 0 159 | let l:need_filter = 0 160 | endif 161 | 162 | let l:list = [] 163 | let l:line = getline('.')[: a:cur_keyword_pos] 164 | 165 | if (&filetype ==# 'lhaskell') 166 | let l:line = substitute(l:line, '^>[ \t]*', '', 'g') 167 | endif 168 | 169 | let [nothing, just_list] = s:multiline_import(l:line, 'list') 170 | if !nothing 171 | return s:filter(just_list, l:cur_keyword_str, 0, l:need_filter) 172 | endif 173 | 174 | if l:line =~# '^import\>.\{-}(' 175 | let l:mod = matchstr(l:line, '^import\s\+\%(qualified\s\+\)\?\zs[^ (]\+') 176 | for [l:sym, l:dict] in items(necoghc#browse(l:mod)) 177 | call add(l:list, { 'word': l:sym, 'menu': s:to_desc(l:mod . '.' . l:sym, l:dict)}) 178 | endfor 179 | return s:filter(l:list, l:cur_keyword_str, 0, l:need_filter) 180 | endif 181 | 182 | let l:syn = s:synname() 183 | if l:line =~# '^import\>' 184 | if !exists('s:list_cache') 185 | let s:list_cache = s:ghc_mod(['list']) 186 | endif 187 | for l:mod in s:list_cache 188 | call add(l:list, { 'word': l:mod, 'menu': l:mod }) 189 | endfor 190 | elseif l:syn =~# 'Pragma' 191 | if l:line[:a:cur_keyword_pos-1] =~# '{-#\s\+$' 192 | for l:p in s:pragmas 193 | call add(l:list, { 'word': l:p, 'menu': l:p }) 194 | endfor 195 | elseif l:line =~# 'LANGUAGE' 196 | if !exists('s:lang_cache') 197 | let s:lang_cache = s:ghc_mod(['lang']) 198 | endif 199 | for l:lang in s:lang_cache 200 | call add(l:list, { 'word': l:lang, 'menu': l:lang }) 201 | call add(l:list, { 'word': 'No' . l:lang, 'menu': 'No' . l:lang }) 202 | endfor 203 | elseif l:line =~# 'OPTIONS_GHC' 204 | if !exists('s:flag_cache') 205 | let s:flag_cache = s:ghc_mod(['flag']) 206 | endif 207 | for l:flag in s:flag_cache 208 | call add(l:list, { 'word': l:flag, 'menu': l:flag }) 209 | endfor 210 | endif 211 | elseif l:cur_keyword_str =~# '\.' 212 | " qualified 213 | let l:idx = matchend(l:cur_keyword_str, '^.*\.') 214 | let l:qual = l:cur_keyword_str[0 : l:idx-2] 215 | let l:name = l:cur_keyword_str[l:idx :] 216 | 217 | for [l:mod, l:opts] in items(necoghc#get_modules()) 218 | if l:mod == l:qual || (has_key(l:opts, 'as') && l:opts.as == l:qual) 219 | for [l:sym, l:dict] in items(necoghc#browse(l:mod)) 220 | call add(l:list, { 'word': l:qual . '.' . l:sym, 'menu': s:to_desc(l:mod . '.' . l:sym, l:dict) }) 221 | endfor 222 | endif 223 | endfor 224 | else 225 | for [l:mod, l:opts] in items(necoghc#get_modules()) 226 | if !l:opts.qualified || l:opts.export 227 | for [l:sym, l:dict] in items(necoghc#browse(l:mod)) 228 | call add(l:list, { 'word': l:sym, 'menu': s:to_desc(l:mod . '.' . l:sym, l:dict) }) 229 | endfor 230 | endif 231 | endfor 232 | endif 233 | 234 | return s:filter(l:list, l:cur_keyword_str, l:need_prefix_filter, 235 | \ l:need_filter) 236 | endfunction "}}} 237 | 238 | " like the following case: 239 | " import Data.List (all 240 | " , 241 | " returns Maybe pos 242 | function! s:multiline_import(cur_text, type) abort "{{{ 243 | if a:cur_text =~# '^\s\+[[:alpha:],(]' 244 | let mod = s:dangling_import(getpos('.')[1]) 245 | if mod != '' 246 | if a:type == 'pos' 247 | let l:idx = matchend(a:cur_text, '^\s\+\%(\ze\%([[:alpha:]]\|([!#$%&*+./<=>?@\\^|~-]\)\|[,(]\s*\)') 248 | if l:idx != -1 249 | return [0, max([matchend(a:cur_text, '^.*,\s*', l:idx), l:idx])] 250 | else 251 | return [0, -1] 252 | endif 253 | else " 'list' 254 | let l:list = [] 255 | for [l:sym, l:dict] in items(necoghc#browse(l:mod)) 256 | call add(l:list, { 'word': l:sym, 'menu': s:to_desc(l:mod . '.' . l:sym, l:dict) }) 257 | endfor 258 | return [0, l:list] 259 | endif 260 | endif 261 | endif 262 | return [1, 0] 263 | endfunction "}}} 264 | 265 | function! necoghc#browse(mod) abort "{{{ 266 | if !has_key(s:browse_cache, a:mod) 267 | call s:ghc_mod_caching_browse(a:mod) 268 | endif 269 | return get(s:browse_cache, a:mod, {}) 270 | endfunction "}}} 271 | 272 | function! s:ghc_mod_caching_browse(mod) abort "{{{ 273 | let l:cmd = ['browse', '-o'] 274 | if get(g:, 'necoghc_enable_detailed_browse') 275 | let l:cmd += ['-d'] 276 | endif 277 | " a callback to supply extra parameters to the `browse` command 278 | " depending on the module name 279 | if exists('*g:NecoghcExtraBrowseOptions') 280 | let l:cmd += g:NecoghcExtraBrowseOptions(a:mod) 281 | endif 282 | let l:cmd += [a:mod] 283 | 284 | if !s:is_async 285 | call s:ghc_mod_caching_async(s:ghc_mod(l:cmd), a:mod) 286 | return 287 | endif 288 | 289 | if len(s:job_info) > s:max_processes 290 | \ || !empty(filter(copy(s:job_info), 'v:val.mod ==# a:mod')) 291 | return 292 | endif 293 | 294 | if has('nvim') 295 | let l:id = jobstart(s:exe_path + l:cmd, { 296 | \ 'on_stdout': function('s:job_handler'), 297 | \ 'on_stderr': function('s:job_handler'), 298 | \ 'on_exit': function('s:job_handler'), 299 | \ }) 300 | let s:job_info[l:id] = { 301 | \ 'candidates': [], 302 | \ 'eof': 0, 303 | \ 'status': -1, 304 | \ 'mod': a:mod, 305 | \ } 306 | elseif s:is_async 307 | try 308 | " Note: In Windows, job_start() does not work in shellslash. 309 | let shellslash = 0 310 | if exists('+shellslash') 311 | let shellslash = &shellslash 312 | set noshellslash 313 | endif 314 | let l:job = job_start(s:exe_path + l:cmd, { 315 | \ 'callback': function('s:job_handler_vim'), 316 | \ 'close_cb': function('s:job_close_callback_vim'), 317 | \ }) 318 | let l:id = s:channel2id(job_getchannel(l:job)) 319 | let s:job_info[l:id] = { 320 | \ 'candidates': [], 321 | \ 'eof': 0, 322 | \ 'status': -1, 323 | \ 'mod': a:mod, 324 | \ 'job': l:job, 325 | \ } 326 | finally 327 | if exists('+shellslash') 328 | let &shellslash = shellslash 329 | endif 330 | endtry 331 | endif 332 | endfunction "}}} 333 | function! s:job_handler_vim(channel, msg) abort "{{{ 334 | call s:job_handler(s:channel2id(a:channel), a:msg, '') 335 | endfunction"}}} 336 | function! s:job_close_callback_vim(channel) abort "{{{ 337 | call s:job_handler(s:channel2id(a:channel), '', 'exit') 338 | endfunction"}}} 339 | function! s:job_handler(id, msg, event) abort "{{{ 340 | if !has_key(s:job_info, a:id) 341 | return 342 | endif 343 | 344 | let job = s:job_info[a:id] 345 | 346 | if a:event ==# 'exit' 347 | let job.eof = 1 348 | let job.status = has('nvim') ? a:msg : 0 349 | call s:ghc_mod_caching_async(job.candidates, job.mod) 350 | call remove(s:job_info, a:id) 351 | return 352 | endif 353 | 354 | let lines = has('nvim') ? 355 | \ map(a:msg, "iconv(v:val, 'char', &encoding)") : 356 | \ split(iconv(a:msg, 'char', &encoding), "\n") 357 | 358 | let candidates = job.candidates 359 | if has('nvim') && !empty(lines) 360 | \ && lines[0] != "\n" && !empty(job.candidates) 361 | " Join to the previous line 362 | let candidates[-1] .= lines[0] 363 | call remove(lines, 0) 364 | endif 365 | 366 | let candidates += lines 367 | endfunction"}}} 368 | function! s:ghc_mod_caching_async(lines, mod) abort "{{{ 369 | let l:dict = {} 370 | for l:line in a:lines 371 | let l:m = matchlist(l:line, '^\(class\|data\|type\|newtype\) \(\S\+\)\( .\+\)\?$') 372 | if !empty(l:m) 373 | let l:dict[l:m[2]] = {'kind': l:m[1], 'args': l:m[3][1 :]} 374 | else 375 | let l:m = matchlist(l:line, '^\(\S\+\) :: \(.\+\)$') 376 | if !empty(l:m) 377 | let l:dict[l:m[1]] = {'type': l:m[2]} 378 | elseif l:line =~# '^\S\+$' 379 | let l:dict[l:line] = {} 380 | elseif l:line != "" 381 | " Maybe some error occurred. 382 | echohl ErrorMsg 383 | echomsg printf('neco-ghc: %s', l:line) 384 | echohl None 385 | endif 386 | endif 387 | endfor 388 | let s:browse_cache[a:mod] = l:dict 389 | endfunction "}}} 390 | function! s:channel2id(channel) abort "{{{ 391 | return get(ch_info(a:channel), 'id') 392 | endfunction"}}} 393 | 394 | function! necoghc#caching_modules() abort "{{{ 395 | let b:necoghc_modules_cache = s:extract_modules() 396 | if s:is_async 397 | for l:mod in keys(b:necoghc_modules_cache) 398 | call necoghc#browse(l:mod) 399 | endfor 400 | endif 401 | endfunction "}}} 402 | 403 | function! necoghc#get_modules() abort "{{{ 404 | if !exists('b:necoghc_modules_cache') 405 | call necoghc#caching_modules() 406 | endif 407 | return b:necoghc_modules_cache 408 | endfunction "}}} 409 | 410 | function! s:ghc_mod(cmd) abort "{{{ 411 | let l:cmd = s:exe_path + a:cmd 412 | let l:lines = split(s:system(l:cmd), '\r\n\|[\r\n]') 413 | 414 | let l:warnings = filter(copy(l:lines), "v:val =~# '^Warning:'") 415 | let l:lines = filter(copy(l:lines), "v:val !~# '^Warning:'") 416 | let l:errors = filter(copy(l:lines), "v:val =~# '^Dummy:0:0:Error:'") 417 | 418 | if empty(l:lines) && get(g:, 'necoghc_debug', 0) 419 | echohl ErrorMsg 420 | echomsg printf('neco-ghc: ' . g:necoghc#executable . ' returned nothing: %s', join(l:cmd, ' ')) 421 | echohl None 422 | endif 423 | 424 | if !empty(l:errors) 425 | if get(g:, 'necoghc_debug', 0) 426 | echohl ErrorMsg 427 | echomsg printf('neco-ghc: ' . g:necoghc#executable . ' returned error messages: %s', join(l:cmd, ' ')) 428 | for l:line in l:errors 429 | echomsg l:line 430 | endfor 431 | echohl None 432 | endif 433 | return [] 434 | endif 435 | 436 | if !empty(l:warnings) 437 | if get(g:, 'necoghc_debug', 0) 438 | echohl ErrorMsg 439 | echomsg printf('neco-ghc: ' . g:necoghc#executable . ' returned warning messages: %s', join(l:cmd, ' ')) 440 | for l:line in l:warnings 441 | echomsg l:line 442 | endfor 443 | echohl None 444 | endif 445 | return [] 446 | endif 447 | 448 | return l:lines 449 | endfunction "}}} 450 | 451 | function! s:extract_modules() abort "{{{ 452 | let l:modules = {'Prelude': {'qualified': 0, 'export': 0}} 453 | 454 | let l:in_module = 0 455 | let l:line = 1 456 | let l:max = min([line('$'), 100]) 457 | while l:line <= l:max 458 | let l:str = getline(l:line) 459 | if l:str =~# '^import\s\+' 460 | let l:idx = matchend(l:str, '^import\s\+') 461 | 462 | " qualified 463 | let l:end = matchend(l:str, '^qualified\s\+', l:idx) 464 | if l:end != -1 465 | let l:qualified = 1 466 | let l:idx = l:end 467 | else 468 | let l:qualified = 0 469 | endif 470 | 471 | let l:name = matchstr(l:str, '^[A-Za-z][A-Za-z0-9.]*', l:idx) 472 | if l:name != '' 473 | if !has_key(l:modules, l:name) 474 | let l:modules[l:name] = { 'qualified': 0, 'export': 0 } 475 | endif 476 | let l:modules[l:name].qualified = l:modules[l:name].qualified || l:qualified 477 | let l:idx = matchend(l:str, '^[A-Za-z][A-Za-z0-9.]*\s*', l:idx) 478 | 479 | " as 480 | let l:end = matchend(l:str, '^as\s\+', l:idx) 481 | if l:end != -1 482 | let l:pattern = "\\%([[:alpha:]_'][[:alnum:]_'.]*\\m\\)" 483 | let l:as = matchstr(l:str, l:pattern, l:end) 484 | let l:modules[l:name].as = l:as 485 | elseif match(l:str, '^(', l:idx) != -1 486 | " exports 487 | let l:modules[l:name].export = 1 488 | endif 489 | endif 490 | elseif l:in_module || l:str =~# '^\s*$' 491 | " skip 492 | elseif l:str =~# '^module\s' 493 | let l:in_module = 1 494 | else 495 | let l:end = matchend(l:str, '^\s*') 496 | let l:syn = s:synname(l:line, l:end+1) 497 | if l:syn !~# 'Pragma' && l:syn !~# 'Comment' 498 | break 499 | endif 500 | endif 501 | 502 | if l:line =~# '\' 503 | let l:in_module = 0 504 | endif 505 | let l:line += 1 506 | endwhile 507 | 508 | return l:modules 509 | endfunction "}}} 510 | 511 | function! s:dangling_import(n) abort "{{{ 512 | let i = a:n 513 | while i >= 1 514 | let line = getline(i) 515 | if line =~# '^import\>' 516 | return matchstr(l:line, '^import\s\+\%(qualified\s\+\)\?\zs[^ (]\+') 517 | elseif line =~# '^\(\s\|--\)' 518 | let i -=1 519 | else 520 | break 521 | endif 522 | endwhile 523 | return 0 524 | endfunction "}}} 525 | 526 | function! necoghc#ghc_mod_version() abort "{{{ 527 | let l:ret = s:system(s:exe_path + ['version']) 528 | if g:necoghc#executable ==# 'hhpc' 529 | return matchstr(l:ret, '\chhpc\%(.exe\)\?\s\+version\s\+\zs\%(\d\+\.\)*\d\+') 530 | elseif g:necoghc#executable ==# 'ghc-mod' 531 | return matchstr(l:ret, '\cghc-mod\%(.exe\)\?\s\+version\s\+\zs\%(\d\+\.\)*\d\+') 532 | endif 533 | endfunction "}}} 534 | 535 | function! s:synname(...) abort "{{{ 536 | if a:0 == 2 537 | let l:line = a:000[0] 538 | let l:col = a:000[1] 539 | else 540 | let l:line = line('.') 541 | let l:col = col('.') - (mode() ==# 'i' ? 1 : 0) 542 | endif 543 | return synIDattr(synID(l:line, l:col, 0), 'name') 544 | endfunction "}}} 545 | 546 | function! s:system(list) abort "{{{ 547 | if !exists('s:exists_vimproc') 548 | try 549 | call vimproc#version() 550 | let s:exists_vimproc = 1 551 | catch 552 | let s:exists_vimproc = 0 553 | endtry 554 | endif 555 | return s:exists_vimproc && !has('nvim') ? 556 | \ vimproc#system(a:list) : system(join(a:list, ' ')) 557 | endfunction "}}} 558 | 559 | function! s:on_haskell() abort "{{{ 560 | call necoghc#caching_modules() 561 | 562 | augroup necoghc 563 | autocmd InsertLeave call necoghc#caching_modules() 564 | augroup END 565 | 566 | command! -buffer -bar -nargs=0 NecoGhcCaching 567 | \ call necoghc#caching_modules() 568 | endfunction "}}} 569 | 570 | " Adapted version of s:find_basedir from 571 | " https://github.com/eagletmt/ghcmod-vim/blob/3e012a5b0b904c5c32eeea39071534d492a64a0f/autoload/ghcmod.vim#L336-L350 572 | function! s:get_ghcmod_root() abort "{{{ 573 | if !exists('b:ghcmod_root') 574 | let l:dir = getcwd() 575 | try 576 | lcd `=fnamemodify(bufname('%'), ':p:h')` 577 | let b:ghcmod_root = 578 | \ substitute(s:system(s:exe_path + ['root']), '\n*$', '', '') 579 | finally 580 | lcd `=l:dir` 581 | endtry 582 | endif 583 | return b:ghcmod_root 584 | endfunction "}}} 585 | 586 | function! s:filter(list, keyword, need_prefix, needed) abort "{{{ 587 | return a:needed ? filter(a:list, 588 | \ 's:word_prefix(v:val, a:keyword, a:need_prefix)' 589 | \ ) : a:list 590 | endfunction "}}} 591 | 592 | " vim: ts=2 sw=2 sts=2 foldmethod=marker 593 | -------------------------------------------------------------------------------- /autoload/necoghc/diagnostics.vim: -------------------------------------------------------------------------------- 1 | function! necoghc#diagnostics#report() abort 2 | let l:debug_flag = get(g:, 'necoghc_debug', 0) 3 | if !l:debug_flag 4 | let g:necoghc_debug = 1 5 | endif 6 | 7 | echomsg 'Current filetype:' &l:filetype 8 | 9 | let l:ghc_mod_executable = executable('ghc-mod') 10 | let l:hhpc_executable = executable('hhpc') 11 | let l:executable = l:ghc_mod_executable || l:hhpc_executable 12 | echomsg 'ghc-mod is executable:' l:ghc_mod_executable 13 | echomsg 'hhpc-mod is executable:' l:hhpc_executable 14 | if !l:executable 15 | echomsg ' Your $PATH:' $PATH 16 | endif 17 | 18 | echomsg 'omnifunc:' &l:omnifunc 19 | echomsg 'neocomplete.vim:' exists(':NeoCompleteEnable') 20 | echomsg 'neocomplcache.vim:' exists(':NeoComplCacheEnable') 21 | echomsg 'YouCompleteMe:' exists(':YcmDebugInfo') 22 | 23 | try 24 | echomsg 'vimproc.vim:' vimproc#version() 25 | catch /^Vim\%((\a\+)\)\=:E117/ 26 | echomsg 'vimproc.vim: not installed' 27 | endtry 28 | 29 | echomsg 'ghc-mod:' necoghc#ghc_mod_version() 30 | 31 | if &l:filetype !=# 'haskell' 32 | call s:error('Run this command in the buffer opening a Haskell file') 33 | return 34 | endif 35 | call necoghc#boot() 36 | echomsg 'Imported modules:' join(keys(necoghc#get_modules()), ', ') 37 | 38 | echomsg 'Number of symbols in Prelude:' len(necoghc#browse('Prelude')) 39 | 40 | if !l:debug_flag 41 | let g:necoghc_debug = 0 42 | endif 43 | endfunction 44 | 45 | function! s:error(msg) abort 46 | echohl ErrorMsg 47 | echomsg a:msg 48 | echohl None 49 | endfunction 50 | -------------------------------------------------------------------------------- /autoload/neocomplcache/sources/ghc.vim: -------------------------------------------------------------------------------- 1 | let s:source = { 2 | \ 'name' : 'ghc', 3 | \ 'kind' : 'ftplugin', 4 | \ 'filetypes': { 'haskell': 1 }, 5 | \ } 6 | 7 | function! s:source.initialize() abort 8 | call necoghc#boot() 9 | endfunction 10 | 11 | function! s:source.get_keyword_pos(cur_text) abort 12 | if neocomplcache#within_comment() 13 | return -1 14 | else 15 | return necoghc#get_keyword_pos(a:cur_text) 16 | endif 17 | endfunction 18 | 19 | function! s:source.get_complete_words(cur_keyword_pos, cur_keyword_str) abort 20 | let l:line = getline('.')[0 : a:cur_keyword_pos] 21 | " force auto-completion on importing functions 22 | if neocomplcache#is_auto_complete() && 23 | \ l:line !~# '^import\>.\{-}(' && 24 | \ l:line !~# '^\s\+[[:alpha:],(]' && 25 | \ len(a:cur_keyword_str) < g:neocomplcache_auto_completion_start_length 26 | return [] 27 | endif 28 | 29 | return necoghc#get_complete_words(a:cur_keyword_pos, a:cur_keyword_str) 30 | endfunction 31 | 32 | function! neocomplcache#sources#ghc#define() abort 33 | if g:necoghc#executable ==# '' 34 | return {} 35 | endif 36 | return s:source 37 | endfunction 38 | 39 | " vim: ts=2 sw=2 sts=2 foldmethod=marker 40 | -------------------------------------------------------------------------------- /autoload/neocomplete/sources/ghc.vim: -------------------------------------------------------------------------------- 1 | let s:source = { 2 | \ 'name' : 'ghc', 3 | \ 'kind' : 'ftplugin', 4 | \ 'filetypes': { 'haskell': 1, 'lhaskell': 1 }, 5 | \ 'min_pattern_length' : 6 | \ g:neocomplete#auto_completion_start_length, 7 | \ 'input_pattern' : '^import\>.\{-}(\|[^. \t0-9]\.\w*', 8 | \ 'hooks' : {}, 9 | \ } 10 | 11 | function! s:source.hooks.on_init(context) abort 12 | call necoghc#boot() 13 | endfunction 14 | 15 | function! s:source.get_complete_position(context) abort 16 | if neocomplete#within_comment() 17 | return -1 18 | endif 19 | 20 | return necoghc#get_keyword_pos(a:context.input) 21 | endfunction 22 | 23 | function! s:source.gather_candidates(context) abort 24 | let line = getline('.')[: a:context.complete_pos] 25 | 26 | return necoghc#get_complete_words( 27 | \ a:context.complete_pos, a:context.complete_str) 28 | endfunction 29 | 30 | function! neocomplete#sources#ghc#define() abort 31 | if g:necoghc#executable ==# '' 32 | return {} 33 | endif 34 | 35 | return s:source 36 | endfunction 37 | 38 | " vim: ts=2 sw=2 sts=2 foldmethod=marker 39 | -------------------------------------------------------------------------------- /neco-ghc.vimup: -------------------------------------------------------------------------------- 1 | "script_name": neco-ghc 2 | "script_id": "3423" 3 | "script_type": utility 4 | "script_package": "{script_name}-{version}.zip" 5 | "required_vim_version": '7.3' 6 | "summary": A neocomplcache plugin for Haskell, using ghc extensions 7 | "detailed_description": | 8 | This plugin supports the following completion on the auto completion framework neocomplcache. 9 | 10 | * pragma http://gyazo.com/c922e323be7dbed9aa70b2bac62be45e.png 11 | * language http://gyazo.com/9df4aa3cf06fc07495d6dd67a4d07cc4.png 12 | * importing a module http://gyazo.com/17a8bf08f3a6d5e123346f5f1c74c5f9.png 13 | * importing a function of a module http://gyazo.com/d3698892a40ffb8e4bef970a02198715.png 14 | * function based on importing modules http://gyazo.com/bc168a8aad5f38c6a83b8aa1b0fb14f6.png 15 | 16 | The development is in github. https://github.com/ujihisa/neco-ghc 17 | 18 | neco-ghc was originally implemented by @eagletmt on July 25, 2010, and then ujihisa added some new features. 19 | "install_details": | 20 | * install the latest neocomplcache.vim 21 | * install ghc-mod package by `cabal install ghc-mod` 22 | * Unarchive neco-ghc and put it into a dir of your &rtp. 23 | "versions": 24 | - '1.2': | 25 | - Follows changes in neocomplcache core 26 | - NeoComplCacheCachingGhcImports command is deprecated 27 | - Introducing NeoComplCacheCachingGhc (for consistency with other 28 | neocomplcache plugins) 29 | - '1.1': Supports operator completion in import statement, using ghc-mod 0.5.3 30 | - '1.0': Initial release in vim.org 31 | # vim: filetype=yaml 32 | -------------------------------------------------------------------------------- /plugin/necoghc.vim: -------------------------------------------------------------------------------- 1 | command! -nargs=0 NecoGhcDiagnostics call necoghc#diagnostics#report() 2 | -------------------------------------------------------------------------------- /rplugin/python3/deoplete/sources/ghc.py: -------------------------------------------------------------------------------- 1 | #============================================================================= 2 | # FILE: ghc.py 3 | # AUTHOR: Shougo Matsushita 4 | #============================================================================= 5 | 6 | from .base import Base 7 | import deoplete.util 8 | import distutils.spawn 9 | import re 10 | 11 | class Source(Base): 12 | def __init__(self, vim): 13 | Base.__init__(self, vim) 14 | 15 | self.name = 'ghc' 16 | self.mark = '[ghc]' 17 | self.filetypes = ['haskell', 'lhaskell'] 18 | self.is_bytepos = True 19 | self.rank = 500 20 | 21 | # force auto-completion on importing functions 22 | self.input_pattern = r'import\s+\w*|[^. \t0-9]\.\w*' 23 | 24 | if distutils.spawn.find_executable('hhpc') is not None: 25 | self.__executable_ghc = self.vim.funcs.executable('hhpc') 26 | elif distutils.spawn.find_executable('ghc-mod') is not None: 27 | self.__executable_ghc = self.vim.funcs.executable('ghc-mod') 28 | else: 29 | self.__executable_ghc = False 30 | 31 | self.__is_booted = False 32 | 33 | def __boot(self): 34 | if self.__is_booted: 35 | return 36 | 37 | self.vim.call('necoghc#boot') 38 | self.__is_booted = True 39 | 40 | def on_event(self, context): 41 | self.__boot() 42 | 43 | def get_complete_position(self, context): 44 | if not self.__executable_ghc: 45 | return -1 46 | 47 | self.__boot() 48 | 49 | return self.vim.call('necoghc#get_keyword_pos', context['input']) 50 | 51 | def gather_candidates(self, context): 52 | return self.vim.call('necoghc#get_complete_words', 53 | context['complete_position'], 54 | context['complete_str']) 55 | --------------------------------------------------------------------------------