├── README.md ├── autoload └── hdevtools.vim └── ftplugin └── haskell └── hdevtools.vim /README.md: -------------------------------------------------------------------------------- 1 | hdevtools Vim Plugin 2 | ==================== 3 | 4 | Vim plugin for Haskell development powered by the lightning fast 5 | [hdevtools]() background server. 6 | 7 | 8 | About 9 | ----- 10 | 11 | [hdevtools]() is a command line program 12 | powered by the GHC API, that provides services for Haskell development. 13 | hdevtools works by running a persistent process in the background, so that your 14 | Haskell modules remain in memory, instead of having to reload everything each 15 | time you change only one file. This is just like `:reload` in GHCi - with 16 | hdevtools you get the speed of GHCi as well as tight integration with your 17 | editor. 18 | 19 | This is the Vim plugin that integrates Vim with hdevtools. 20 | 21 | 22 | Requirements 23 | ------------ 24 | 25 | The *vim-hdevtools* plugin requires a way to run `hdevtools`. Here are the 26 | ways to make `hdevtools` available to the plugin. 27 | 28 | ### Global `hdevtools` 29 | 30 | Install the `hdevtools` command line program so that it is on your 31 | executable `$PATH`. 32 | 33 | Get it from Github: 34 | 35 | Or from Hackage: 36 | 37 | $ cabal install hdevtools 38 | 39 | Or from Stackage: 40 | 41 | $ stack install hdevtools 42 | 43 | Note that `hdevtools` must be built with the same version of GHC as the 44 | project which you will be editing. 45 | 46 | ### Local `hdevtools` with `stack` 47 | 48 | If your project is built with [stack]() and 49 | if you run Vim from the directory that contains `stack.yaml`, then 50 | the *vim-hdevtools* plugin can employ `stack` to automatically install 51 | `hdevtools` built with the same version of GHC indicated by the resolver 52 | in your `stack.yaml`. This will not conflict with any other installations 53 | of `hdevtools` on your system. 54 | 55 | If you want the *vim-hdevtools* plugin to use `stack`, 56 | you have to enable this feature in your `.vimrc` like so: 57 | 58 | let g:hdevtools_stack = 1 59 | 60 | 61 | Installation 62 | ------------ 63 | 64 | 1. Install this plugin. [pathogen.vim]() 65 | is the recommended way: 66 | 67 | cd ~/.vim/bundle 68 | git clone https://github.com/bitc/vim-hdevtools.git 69 | 70 | 2. Configure your keybindings in your `.vimrc` file. I recommend something 71 | like: 72 | 73 | au FileType haskell nnoremap :HdevtoolsType 74 | au FileType haskell nnoremap :HdevtoolsInfo 75 | au FileType haskell nnoremap :HdevtoolsClear 76 | 77 | 78 | Features 79 | -------- 80 | 81 | ### Type Checking ### 82 | 83 | The best feature of the hdevtools command is near-instant checking of Haskell 84 | source files for errors - it's fast even for huge projects. 85 | 86 | This Vim plugin does not have direct support for interacting with this feature. 87 | Instead, I recommend using the excellent 88 | [Syntastic]() plugin. 89 | 90 | ### Type Information ### 91 | 92 | Position the cursor anywhere in a Haskell source file, and execute 93 | `HdevtoolsType` (or press the ``) key if you have configured as above). 94 | 95 | The type for the expression under the cursor will be printed, and the 96 | expression will be highlighted. Repeated presses will expand the expression 97 | that is examined. 98 | 99 | To get information from GHC about the identifier under cursor, 100 | execute `HdevtoolsInfo` (or press the `` key as configured above). 101 | 102 | You can execute `HdevtoolsClear` to get rid of the highlighting. 103 | 104 | Customization 105 | ------------- 106 | 107 | You can set the `g:hdevtools_options` variable to pass custom options to 108 | hdevtools. 109 | 110 | This is useful for passing options through to GHC with the hdevtools `-g` 111 | flag. For example, if your project source code is in a `src` directory, 112 | and you want to use the GHC option `-Wall`, then stick the following somewhere 113 | appropriate (such as your project's `Session.vim`): 114 | 115 | let g:hdevtools_options = '-g-isrc -g-Wall' 116 | 117 | Make sure that each GHC option has its own `-g` prefix (don't group multiple 118 | options like this: `"-g-isrc\ -Wall"`) 119 | 120 | I recommend setting the flag to 121 | [defer GHC type errors to runtime](), 122 | so that Haskell expressions can be typechecked even if type errors 123 | elsewhere in the project would otherwise prevent GHC from compiling. 124 | 125 | let g:hdevtools_options = '-g-fdefer-type-errors' 126 | 127 | Credits 128 | ------- 129 | 130 | Parts of the design of this plugin were inspired by 131 | [ghcmod-vim](), and large amounts of 132 | code were also taken. 133 | -------------------------------------------------------------------------------- /autoload/hdevtools.vim: -------------------------------------------------------------------------------- 1 | let s:hdevtools_info_buffer = -1 2 | 3 | function! s:shutdown() 4 | let l:cmd = hdevtools#build_command_bare('admin', '--stop-server') 5 | " Must save the output in order for the command to actually run: 6 | let l:dummy = system(l:cmd) 7 | endfunction 8 | 9 | function! hdevtools#prepare_shutdown() 10 | let l:cmd = hdevtools#build_command_bare('admin', '--status') 11 | " Must save the output in order for the command to actually run: 12 | let l:dummy = system(l:cmd) 13 | 14 | " Only shutdown the hdevtools server on Vim quit if the above 'status' 15 | " command indicated that the hdevtools server isn't currently running: This 16 | " plugin will start the server, so this plugin should be responsible for 17 | " shutting it down when Vim exits. 18 | " 19 | " If on the other hand, the hdevtools server is already running, then we 20 | " shouldn't shut it down on Vim exit, since someone else started it, so it's 21 | " their problem. 22 | if v:shell_error != 0 23 | autocmd VimLeave * call s:shutdown() 24 | endif 25 | endfunction 26 | 27 | function! hdevtools#go_file(opencmd) 28 | " Get the import declaration under the cursor 29 | let l:module_name = matchstr(getline("."), '^import\s\+\(qualified\s\+\)\=\zs\(\w\|\.\)\+\ze') 30 | if l:module_name ==# '' 31 | call hdevtools#print_warning("Cursor not on a Haskell import declaration") 32 | return 33 | endif 34 | 35 | let l:cmd = hdevtools#build_command('modulefile', shellescape(l:module_name)) 36 | let l:output = system(l:cmd) 37 | 38 | let l:lines = split(l:output, '\n') 39 | 40 | if v:shell_error != 0 41 | for l:line in l:lines 42 | call hdevtools#print_error(l:line) 43 | endfor 44 | return 45 | endif 46 | 47 | exe a:opencmd fnameescape(l:lines[0]) 48 | endfunction 49 | 50 | function! hdevtools#info(identifier) 51 | let l:identifier = a:identifier 52 | 53 | if l:identifier ==# '' 54 | " No identifier argument given, probably called from a keyboard shortcut 55 | 56 | if bufnr('%') == s:hdevtools_info_buffer 57 | " The Info Window is already open and active, so simply close it and 58 | " finish 59 | call hdevtools#infowin_leave() 60 | return 61 | endif 62 | 63 | " Get the identifier under the cursor 64 | let l:identifier = s:extract_identifier(getline("."), col(".")) 65 | endif 66 | 67 | if l:identifier ==# '' 68 | echo '-- No Identifier Under Cursor' 69 | return 70 | endif 71 | 72 | let l:file = expand('%') 73 | if l:file ==# '' 74 | call hdevtools#print_warning("current version of hdevtools.vim doesn't support running on an unnamed buffer.") 75 | return 76 | endif 77 | let l:cmd = hdevtools#build_command('info', shellescape(l:file) . ' -- ' . shellescape(l:identifier)) 78 | let l:output = system(l:cmd) 79 | 80 | let l:lines = split(l:output, '\n') 81 | 82 | " Check if the call to hdevtools info succeeded 83 | if v:shell_error != 0 84 | for l:line in l:lines 85 | call hdevtools#print_error(l:line) 86 | endfor 87 | return 88 | endif 89 | 90 | " Create a new window 91 | call s:infowin_create("(" . l:identifier . ")") 92 | 93 | " Adjust the height of the Info Window so that all lines will fit 94 | exe 'resize ' (len(l:lines) + 1) 95 | 96 | " The result returned from the 'info' command is very similar to regular 97 | " haskell code, so Haskell syntax highlighting looks good on it 98 | setlocal filetype=haskell 99 | 100 | " Fill the contents of the Info Window with the result 101 | setlocal modifiable 102 | call append(0, l:lines) 103 | setlocal nomodifiable 104 | 105 | " Jump the cursor to the beginning of the buffer 106 | normal gg 107 | 108 | " Look for the first line containing a reference to a file and jump the 109 | " cursor to it if found 110 | for l:i in range(0, len(l:lines)-1) 111 | if match(l:lines[l:i], '-- Defined at \S\+:\d\+:\d\+') >= 0 112 | call setpos(".", [0, l:i + 1, 1, 0]) 113 | break 114 | endif 115 | endfor 116 | 117 | " Apply syntax highlighting for these comments: -- Defined at Hello.hs:12:5 118 | " These are turned into links that can be jumped to 119 | syntax match HdevtoolsInfoLink '-- Defined at \zs\S\+:\d\+:\d\+' containedin=ALL contained 120 | highlight link HdevtoolsInfoLink Underlined 121 | endfunction 122 | 123 | function! s:extract_identifier(line_text, col) 124 | if a:col > len(a:line_text) 125 | return '' 126 | endif 127 | 128 | let l:index = a:col - 1 129 | let l:delimiter = '\s\|[(),;`{}"[\]]' 130 | 131 | " Move the index forward till the cursor is not on a delimiter 132 | while match(a:line_text[l:index], l:delimiter) == 0 133 | let l:index = l:index + 1 134 | if l:index == len(a:line_text) 135 | return '' 136 | endif 137 | endwhile 138 | 139 | let l:start_index = l:index 140 | " Move start_index backwards until it hits a delimiter or beginning of line 141 | while l:start_index > 0 && match(a:line_text[l:start_index-1], l:delimiter) < 0 142 | let l:start_index = l:start_index - 1 143 | endwhile 144 | 145 | let l:end_index = l:index 146 | " Move end_index forwards until it hits a delimiter or end of line 147 | while l:end_index < len(a:line_text) - 1 && match(a:line_text[l:end_index+1], l:delimiter) < 0 148 | let l:end_index = l:end_index + 1 149 | endwhile 150 | 151 | let l:fragment = a:line_text[l:start_index : l:end_index] 152 | let l:index = l:index - l:start_index 153 | 154 | let l:results = [] 155 | 156 | let l:name_regex = '\(\u\(\w\|''\)*\.\)*\(\a\|_\)\(\w\|''\)*' 157 | let l:operator_regex = '\(\u\(\w\|''\)*\.\)*\(\\\|[-!#$%&*+./<=>?@^|~:]\)\+' 158 | 159 | " Perform two passes over the fragment(one for finding a name, and the other 160 | " for finding an operator). Each pass tries to find a match that has the 161 | " cursor contained within it. 162 | for l:regex in [l:name_regex, l:operator_regex] 163 | let l:remainder = l:fragment 164 | let l:rindex = l:index 165 | while 1 166 | let l:i = match(l:remainder, l:regex) 167 | if l:i < 0 168 | break 169 | endif 170 | let l:result = matchstr(l:remainder, l:regex) 171 | let l:end = l:i + len(l:result) 172 | if l:i <= l:rindex && l:end > l:rindex 173 | call add(l:results, l:result) 174 | break 175 | endif 176 | let l:remainder = l:remainder[l:end :] 177 | let l:rindex = l:rindex - l:end 178 | endwhile 179 | endfor 180 | 181 | " There can be at most 2 matches(one from each pass). The longest one is the 182 | " correct one. 183 | if len(l:results) == 0 184 | return '' 185 | elseif len(l:results) == 1 186 | return l:results[0] 187 | else 188 | if len(l:results[0]) > len(l:results[1]) 189 | return l:results[0] 190 | else 191 | return l:results[1] 192 | endif 193 | endif 194 | endfunction 195 | 196 | " Unit Test 197 | function! hdevtools#test_extract_identifier() 198 | let l:tests = [ 199 | \ 'let #foo# = 5', 200 | \ '#main#', 201 | \ '1 #+# 1', 202 | \ '1#+#1', 203 | \ 'blah #Foo.Bar# blah', 204 | \ 'blah #Foo.bar# blah', 205 | \ 'blah #foo#.Bar blah', 206 | \ 'blah #foo#.bar blah', 207 | \ 'blah foo#.#Bar blah', 208 | \ 'blah foo#.#bar blah', 209 | \ 'blah foo.#bar# blah', 210 | \ 'blah foo.#Bar# blah', 211 | \ 'blah #A.B.C.d# blah', 212 | \ '#foo#+bar', 213 | \ 'foo+#bar#', 214 | \ '#Foo#+bar', 215 | \ 'foo+#Bar#', 216 | \ '#Prelude..#', 217 | \ '[#foo#..bar]', 218 | \ '[foo..#bar#]', 219 | \ '#Foo.bar#', 220 | \ '#Foo#*bar', 221 | \ 'Foo#*#bar', 222 | \ 'Foo*#bar#', 223 | \ '#Foo.foo#.bar', 224 | \ 'Foo.foo#.#bar', 225 | \ 'Foo.foo.#bar#', 226 | \ '"a"#++#"b"', 227 | \ '''a''#<#''b''', 228 | \ '#Foo.$#', 229 | \ 'foo.#Foo.$#', 230 | \ '#-#', 231 | \ '#/#', 232 | \ '#\#', 233 | \ '#@#' 234 | \ ] 235 | for l:test in l:tests 236 | let l:expected = matchstr(l:test, '#\zs.*\ze#') 237 | let l:input = substitute(l:test, '#', '', 'g') 238 | let l:start_index = match(l:test, '#') + 1 239 | let l:end_index = match(l:test, '\%>' . l:start_index . 'c#') - 1 240 | for l:i in range(l:start_index, l:end_index) 241 | let l:result = s:extract_identifier(l:input, l:i) 242 | if l:expected !=# l:result 243 | call hdevtools#print_error("TEST FAILED expected: (" . l:expected . ") got: (" . l:result . ") for column " . l:i . " of: " . l:input) 244 | endif 245 | endfor 246 | endfor 247 | endfunction 248 | 249 | " ---------------------------------------------------------------------------- 250 | " The window code below was adapted from the 'Command-T' plugin, with major 251 | " changes (and translated from the original Ruby) 252 | " 253 | " Command-T: 254 | " https://wincent.com/products/command-t/ 255 | 256 | function! s:infowin_create(window_title) 257 | let s:initial_window = winnr() 258 | call s:window_dimensions_save() 259 | 260 | " The following settings are global, so they must be saved before being 261 | " changed so that they can be later restored. 262 | " If you add to the code below changes to additional global settings, then 263 | " you must also appropriately modify s:settings_save and s:settings_restore 264 | call s:settings_save() 265 | set noinsertmode " don't make Insert mode the default 266 | set report=9999 " don't show 'X lines changed' reports 267 | set sidescroll=0 " don't sidescroll in jumps 268 | set sidescrolloff=0 " don't sidescroll automatically 269 | set noequalalways " don't auto-balance window sizes 270 | 271 | " The following settings are local so they don't have to be saved 272 | exe 'silent! botright 1split' fnameescape(a:window_title) 273 | setlocal bufhidden=unload " unload buf when no longer displayed 274 | setlocal buftype=nofile " buffer is not related to any file 275 | setlocal nomodifiable " prevent manual edits 276 | setlocal noswapfile " don't create a swapfile 277 | setlocal nowrap " don't soft-wrap 278 | setlocal nonumber " don't show line numbers 279 | setlocal nolist " don't use List mode (visible tabs etc) 280 | setlocal foldcolumn=0 " don't show a fold column at side 281 | setlocal foldlevel=99 " don't fold anything 282 | setlocal nocursorline " don't highlight line cursor is on 283 | setlocal nospell " spell-checking off 284 | setlocal nobuflisted " don't show up in the buffer list 285 | setlocal textwidth=0 " don't hard-wrap (break long lines) 286 | 287 | " Save the buffer number of the Info Window for later 288 | let s:hdevtools_info_buffer = bufnr("%") 289 | 290 | " Key bindings for the Info Window 291 | nnoremap :call hdevtools#infowin_jump() 292 | nnoremap :call hdevtools#infowin_jump('sp') 293 | nnoremap :call hdevtools#infowin_leave() 294 | 295 | " perform cleanup using an autocmd to ensure we don't get caught out by some 296 | " unexpected means of dismissing or leaving the Info Window (eg. , 297 | " etc) 298 | autocmd! * 299 | autocmd BufLeave silent! call hdevtools#infowin_leave() 300 | autocmd BufUnload silent! call s:infowin_unload() 301 | endfunction 302 | 303 | function! s:settings_save() 304 | " The following must be in sync with settings_restore 305 | let s:original_settings = [ 306 | \ &report, 307 | \ &sidescroll, 308 | \ &sidescrolloff, 309 | \ &equalalways, 310 | \ &insertmode 311 | \ ] 312 | endfunction 313 | 314 | function! s:settings_restore() 315 | " The following must be in sync with settings_save 316 | let &report = s:original_settings[0] 317 | let &sidescroll = s:original_settings[1] 318 | let &sidescrolloff = s:original_settings[2] 319 | let &equalalways = s:original_settings[3] 320 | let &insertmode = s:original_settings[4] 321 | endfunction 322 | 323 | function! s:window_dimensions_save() 324 | " Each element of the list s:window_dimensions is a list of 3 integers of 325 | " the form: [id, width, height] 326 | let s:window_dimensions = [] 327 | for l:i in range(1, winnr("$")) 328 | call add(s:window_dimensions, [l:i, winwidth(i), winheight(i)]) 329 | endfor 330 | endfunction 331 | 332 | " Used in s:window_dimensions_restore for sorting the windows 333 | function! hdevtools#compare_window(i1, i2) 334 | " Compare the window heights: 335 | if a:i1[2] < a:i2[2] 336 | return 1 337 | elseif a:i1[2] > a:i2[2] 338 | return -1 339 | endif 340 | " The heights were equal, so compare the widths: 341 | if a:i1[1] < a:i2[1] 342 | return 1 343 | elseif a:i1[1] > a:i2[1] 344 | return -1 345 | endif 346 | " The widths were also equal: 347 | return 0 348 | endfunction 349 | 350 | function! s:window_dimensions_restore() 351 | " sort from tallest to shortest, tie-breaking on window width 352 | call sort(s:window_dimensions, "hdevtools#compare_window") 353 | 354 | " starting with the tallest ensures that there are no constraints preventing 355 | " windows on the side of vertical splits from regaining their original full 356 | " size 357 | for l:i in s:window_dimensions 358 | let l:id = l:i[0] 359 | let l:width = l:i[1] 360 | let l:height = l:i[2] 361 | exe l:id . "wincmd w" 362 | exe "resize" l:height 363 | exe "vertical resize" l:width 364 | endfor 365 | endfunction 366 | 367 | function! hdevtools#infowin_leave() 368 | call s:infowin_close() 369 | call s:infowin_unload() 370 | let s:hdevtools_info_buffer = -1 371 | endfunction 372 | 373 | function! s:infowin_unload() 374 | call s:window_dimensions_restore() 375 | call s:settings_restore() 376 | exe s:initial_window . "wincmd w" 377 | endfunction 378 | 379 | function! s:infowin_close() 380 | exe "silent! bunload!" s:hdevtools_info_buffer 381 | endfunction 382 | 383 | " Jumps to the location under the cursor. 384 | " 385 | " An single optional argument is allowed, which is a string command for 386 | " opening a window, for example 'split' or 'vsplit'. 387 | " 388 | " If no argument is supplied then the default is to try to reuse the existing 389 | " window (using 'edit') unless it is unsaved and cannot be changed, in which 390 | " case 'split' is used 391 | function! hdevtools#infowin_jump(...) 392 | " Search for the filepath, line and column in the current line that matches 393 | " the format: -- Defined at Hello.hs:12:5 394 | let l:line = getline(".") 395 | let l:m = matchlist(line, '-- Defined at \(\S\+\):\(\d\+\):\(\d\+\)') 396 | 397 | if len(l:m) == 0 398 | " No match found on the current line 399 | return 400 | endif 401 | 402 | " Extract the values from the result of the previous regex 403 | let l:filepath = l:m[1] 404 | let l:row = l:m[2] 405 | let l:col = l:m[3] 406 | 407 | " Get rid of the Info Window; the user doesn't need it anymore 408 | call hdevtools#infowin_leave() 409 | 410 | " Open the file in a window as appropriate 411 | if a:0 > 0 && a:1 !=# '' 412 | exe "silent" a:1 fnameescape(l:filepath) 413 | else 414 | if l:filepath !=# bufname("%") 415 | if !&hidden && &modified 416 | let l:opencmd = "sp" 417 | else 418 | let l:opencmd = "e" 419 | endif 420 | exe "silent" l:opencmd fnameescape(l:filepath) 421 | endif 422 | endif 423 | 424 | " Jump the cursor to the position from the 'Defined at' 425 | call setpos(".", [0, l:row, l:col, 0]) 426 | endfunction 427 | 428 | " ---------------------------------------------------------------------------- 429 | " Most of the code below has been taken from ghcmod-vim, with a few 430 | " adjustments and tweaks. 431 | " 432 | " ghcmod-vim: 433 | " https://github.com/eagletmt/ghcmod-vim/ 434 | 435 | let s:hdevtools_type = { 436 | \ 'ix': 0, 437 | \ 'types': [], 438 | \ } 439 | 440 | function! s:hdevtools_type.spans(line, col) 441 | if empty(self.types) 442 | return 0 443 | endif 444 | let [l:line1, l:col1, l:line2, l:col2] = self.types[self.ix][0] 445 | return l:line1 <= a:line && a:line <= l:line2 && l:col1 <= a:col && a:col <= l:col2 446 | endfunction 447 | 448 | function! s:hdevtools_type.type() 449 | return self.types[self.ix] 450 | endfunction 451 | 452 | function! s:hdevtools_type.incr_ix() 453 | let self.ix = (self.ix + 1) % len(self.types) 454 | endfunction 455 | 456 | function! s:hdevtools_type.highlight(group) 457 | if empty(self.types) 458 | return 459 | endif 460 | call hdevtools#clear_highlight() 461 | let [l:line1, l:col1, l:line2, l:col2] = self.types[self.ix][0] 462 | let w:hdevtools_type_matchid = matchadd(a:group, '\%' . l:line1 . 'l\%' . l:col1 . 'c\_.*\%' . l:line2 . 'l\%' . l:col2 . 'c') 463 | endfunction 464 | 465 | function! s:highlight_group() 466 | return get(g:, 'hdevtools_type_highlight', 'Visual') 467 | endfunction 468 | 469 | function! s:on_enter() 470 | if exists('b:hdevtools_type') 471 | call b:hdevtools_type.highlight(s:highlight_group()) 472 | endif 473 | endfunction 474 | 475 | function! s:on_leave() 476 | call hdevtools#clear_highlight() 477 | endfunction 478 | 479 | function! hdevtools#build_command(command, args) 480 | let l:cmd = g:hdevtools_exe . ' ' . a:command . ' ' 481 | let l:cmd = l:cmd . get(g:, 'hdevtools_options', '') . ' ' 482 | let l:cmd = l:cmd . a:args 483 | return l:cmd 484 | endfunction 485 | 486 | " Does not include g:hdevtools_options 487 | function! hdevtools#build_command_bare(command, args) 488 | let l:cmd = g:hdevtools_exe . ' ' . a:command . ' ' 489 | let l:cmd = l:cmd . a:args 490 | return l:cmd 491 | endfunction 492 | 493 | function! hdevtools#clear_highlight() 494 | if exists('w:hdevtools_type_matchid') 495 | call matchdelete(w:hdevtools_type_matchid) 496 | unlet w:hdevtools_type_matchid 497 | endif 498 | endfunction 499 | 500 | function! hdevtools#type() 501 | if &l:modified 502 | call hdevtools#print_warning('hdevtools#type: the buffer has been modified but not written') 503 | endif 504 | let l:line = line('.') 505 | let l:col = col('.') 506 | if exists('b:hdevtools_type') && b:hdevtools_type.spans(l:line, l:col) 507 | call b:hdevtools_type.incr_ix() 508 | call b:hdevtools_type.highlight(s:highlight_group()) 509 | return b:hdevtools_type.type() 510 | endif 511 | 512 | let l:file = expand('%') 513 | if l:file ==# '' 514 | call hdevtools#print_warning("current version of hdevtools.vim doesn't support running on an unnamed buffer.") 515 | return ['', ''] 516 | endif 517 | let l:cmd = hdevtools#build_command('type', shellescape(l:file) . ' ' . l:line . ' ' . l:col) 518 | let l:output = system(l:cmd) 519 | 520 | if v:shell_error != 0 521 | for l:line in split(l:output, '\n') 522 | call hdevtools#print_error(l:line) 523 | endfor 524 | return 525 | endif 526 | 527 | let l:types = [] 528 | for l:line in split(l:output, '\n') 529 | let l:m = matchlist(l:line, '\(\d\+\) \(\d\+\) \(\d\+\) \(\d\+\) "\([^"]\+\)"') 530 | if len(l:m) != 0 531 | call add(l:types, [l:m[1 : 4], l:m[5]]) 532 | endif 533 | endfor 534 | 535 | call hdevtools#clear_highlight() 536 | 537 | let l:len = len(l:types) 538 | if l:len == 0 539 | return [0, '-- No Type Information'] 540 | endif 541 | 542 | let b:hdevtools_type = deepcopy(s:hdevtools_type) 543 | 544 | let b:hdevtools_type.types = l:types 545 | let l:ret = b:hdevtools_type.type() 546 | let [l:line1, l:col1, l:line2, l:col2] = l:ret[0] 547 | call b:hdevtools_type.highlight(s:highlight_group()) 548 | 549 | augroup hdevtools-type-highlight 550 | autocmd! * 551 | autocmd BufEnter call s:on_enter() 552 | autocmd WinEnter call s:on_enter() 553 | autocmd BufLeave call s:on_leave() 554 | autocmd WinLeave call s:on_leave() 555 | augroup END 556 | 557 | return l:ret 558 | endfunction 559 | 560 | function! hdevtools#type_clear() 561 | if exists('b:hdevtools_type') 562 | call hdevtools#clear_highlight() 563 | unlet b:hdevtools_type 564 | endif 565 | endfunction 566 | 567 | function! hdevtools#print_error(msg) 568 | echohl ErrorMsg 569 | echomsg a:msg 570 | echohl None 571 | endfunction 572 | 573 | function! hdevtools#print_warning(msg) 574 | echohl WarningMsg 575 | echomsg a:msg 576 | echohl None 577 | endfunction 578 | -------------------------------------------------------------------------------- /ftplugin/haskell/hdevtools.vim: -------------------------------------------------------------------------------- 1 | if exists('b:did_ftplugin_hdevtools') && b:did_ftplugin_hdevtools 2 | finish 3 | endif 4 | let b:did_ftplugin_hdevtools = 1 5 | 6 | if !exists('s:has_hdevtools') 7 | let s:has_hdevtools = 0 8 | 9 | " For stack support, vim must be started in the directory containing stack.yaml 10 | if exists('g:hdevtools_stack') && g:hdevtools_stack && filereadable("stack.yaml") 11 | if !executable('stack') 12 | call hdevtools#print_error('hdevtools: stack.yaml found, but stack is not executable!') 13 | finish 14 | endif 15 | let g:hdevtools_exe = 'stack exec --silent --no-ghc-package-path --package hdevtools hdevtools --' 16 | elseif executable('hdevtools') 17 | let g:hdevtools_exe = 'hdevtools' 18 | else 19 | call hdevtools#print_error('hdevtools: hdevtools is not executable!') 20 | finish 21 | endif 22 | 23 | let s:has_hdevtools = 1 24 | endif 25 | 26 | if !s:has_hdevtools 27 | finish 28 | endif 29 | 30 | call hdevtools#prepare_shutdown() 31 | 32 | if exists('b:undo_ftplugin') 33 | let b:undo_ftplugin .= ' | ' 34 | else 35 | let b:undo_ftplugin = '' 36 | endif 37 | 38 | nnoremap gf :call hdevtools#go_file("e") 39 | nnoremap :call hdevtools#go_file("sp") 40 | 41 | command! -buffer -nargs=0 HdevtoolsType echo hdevtools#type()[1] 42 | command! -buffer -nargs=0 HdevtoolsClear call hdevtools#type_clear() 43 | command! -buffer -nargs=? HdevtoolsInfo call hdevtools#info() 44 | 45 | let b:undo_ftplugin .= join(map([ 46 | \ 'HdevtoolsType', 47 | \ 'HdevtoolsClear', 48 | \ 'HdevtoolsInfo' 49 | \ ], '"delcommand " . v:val'), ' | ') 50 | let b:undo_ftplugin .= ' | unlet b:did_ftplugin_hdevtools' 51 | --------------------------------------------------------------------------------