├── 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 |
--------------------------------------------------------------------------------