Skip to content

Commit 2fa5812

Browse files
authored
Merge pull request #10 from ionide/fsi
F# Interactive Integration
2 parents 5f4f194 + 485dbd1 commit 2fa5812

File tree

3 files changed

+294
-13
lines changed

3 files changed

+294
-13
lines changed

README.mkd

Lines changed: 94 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
**F# support for Vim/Neovim**
44

5+
![ionide-vim](https://i.imgur.com/3RLcJw6.gif)
6+
57
_Part of the [Ionide](http://ionide.io) plugin suite._
68

79
## About Ionide-Vim
@@ -40,7 +42,7 @@ Feel free to [request features and/or file bug reports](https://github.com/ionid
4042

4143
- Syntax highlighting
4244
- Auto completions
43-
- Error highlighting
45+
- Error highlighting and error list
4446
- Tooltips
4547
- Go to Definition
4648
- Find all references
@@ -49,6 +51,7 @@ Feel free to [request features and/or file bug reports](https://github.com/ionid
4951
- Show symbols in file
5052
- Find symbol in workspace
5153
- Show signature in status line
54+
- Integration with F# Interactive **(new!)**
5255

5356
## Getting Started
5457

@@ -92,6 +95,8 @@ let g:LanguageClient_serverCommands = {
9295

9396
This will configure FSAC to be used from LanguageClient-neovim.
9497

98+
Note: if you append it before actually installing Ionide-vim, it will fail with `E121: Undefined variable: g:fsharp#languageserver_command`.
99+
95100
## Usage
96101

97102
Opening either `*.fs`, `*.fsi` or `*.fsx` files should trigger syntax highlighting and other depending runtime files as well.
@@ -103,52 +108,131 @@ Refer to [LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neov
103108
To be added as requested for F#-specific features.
104109

105110
#### `:FSharpLoadWorkspaceAuto`
106-
- Search a workspace (`sln` or `fsproj`) and then load it.
111+
- Searches a workspace (`sln` or `fsproj`) and then load it.
107112
- Equivalent to `FSharp.workspaceMode = sln` in Ionide-VSCode.
108113
- Automatically called when you open F# files. Can be disabled in settings.
109114
- The deep level of directory hierarchy to search can also be configured in settings.
110115

111116
#### `:FSharpParseProject <files>+`
112-
- Load specified projects (`sln` or `fsproj`).
117+
- Loads specified projects (`sln` or `fsproj`).
113118

114119
#### `:FSharpReloadWorkspace`
115-
- Reload all the projects currently loaded.
120+
- Reloads all the projects currently loaded.
116121
- Automatically called when you save `.fsproj` files. Can be disabled in settings.
117122

118123
#### `:FSharpUpdateFSAC`
119-
- Download the latest build of FsAutoComplete to be used with Ionide-vim.
124+
- Downloads the latest build of FsAutoComplete to be used with Ionide-vim.
125+
126+
### Working with F# Interactive
127+
128+
Ionide-vim has an integration with F# Interactive.
129+
130+
FSI is displayed using the builtin `:terminal` feature introduced in Vim 8 / Neovim and can be used like in VSCode.
131+
132+
#### `:FsiShow`
133+
- Shows F# Interactive windows.
134+
135+
#### `:FsiEval <expr>`
136+
- Evaluates given expression in FSI.
137+
138+
#### `:FsiEvalBuffer`
139+
- Sends the content of current file to FSI.
140+
141+
#### `:FsiReset`
142+
- Resets the current FSI session.
143+
144+
#### `Alt-Enter`
145+
- When in normal mode, sends the current line to FSI.
146+
- When in visual mode, sends the selection to FSI.
147+
- Sending code to FSI opens FSI window but the cursor does not focus to it. Unlike Neovim, Vim doesn't support asynchronous buffer updating so you have to input something (e.g. moving cursor) to see the result. You can change this behavior in settings.
148+
149+
#### `Alt-@`
150+
- Toggles FSI window. FSI windows shown in different tabpages share the same FSI session.
151+
- When opened, the cursor automatically focuses to the FSI window (unlike in `Alt-Enter` by default).
152+
153+
You can customize the location of FSI, key mappings, etc. See [the documentation below](#f-interactive-settings).
120154

121155
### Settings
122156

123157
Refer to [LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim) for features provided via Language Server Protocol.
124158

125159
To be added as requested for F#-specific features.
126160

127-
#### Enable/disable automatic calling of `:FSharpLoadWorkspaceAuto` on opening F# files (default: 1)
161+
#### Workspace Settings
162+
163+
##### Enable/disable automatic calling of `:FSharpLoadWorkspaceAuto` on opening F# files (default: 1)
128164

129165
~~~.vim
130166
let g:fsharp#automatic_workspace_init = 1 " 0 to disable.
131167
~~~
132168

133-
#### Set the deep level of directory hierarchy when searching for sln/fsprojs (default: 2)
169+
##### Set the deep level of directory hierarchy when searching for sln/fsprojs (default: 2)
134170

135171
~~~.vim
136172
let g:fsharp#workspace_mode_peek_deep_level = 2
137173
138174
~~~
139175

140-
#### Enable/disable automatic calling of `:FSharpReloadWorkspace` on saving `fsproj` (default: 1)
176+
##### Enable/disable automatic calling of `:FSharpReloadWorkspace` on saving `fsproj` (default: 1)
141177

142178
~~~.vim
143179
let g:fsharp#automatic_reload_workspace = 1 " 0 to disable.
144180
~~~
145181

146-
#### Show type signature at cursor position (default: 1)
182+
#### Editor Settings
183+
184+
##### Show type signature at cursor position (default: 1)
147185

148186
~~~.vim
149187
let g:fsharp#show_signature_on_cursor_move = 1 " 0 to disable.
150188
~~~
151189

190+
#### F# Interactive Settings
191+
192+
##### Change the F# Interactive command to be used within Ionide-vim (default: `dotnet fsi`)
193+
194+
~~~.vim
195+
let g:fsharp#fsi_command = "fsharpi"
196+
~~~
197+
198+
##### Customize how FSI window is opened (default: `botright 10new`)
199+
200+
It must create a new empty window and then focus to it.
201+
202+
See [`:help opening-window`](http://vimdoc.sourceforge.net/htmldoc/windows.html#opening-window) for details.
203+
204+
~~~.vim
205+
let g:fsharp#fsi_window_command = "botright vnew"
206+
~~~
207+
208+
##### Set if sending line/selection to FSI shoule make the cursor focus to FSI window (default: `0`)
209+
210+
If you are using Vim, you might want to enable this to see the result without inputting something.
211+
212+
~~~.vim
213+
let g:fsharp#fsi_focus_on_send = 1 " 0 to not to focus.
214+
~~~
215+
216+
##### Change the key mappings (default: `vscode`)
217+
218+
* `vscode`: Default. Same as in Ionide-VSCode (`Alt-Enter` to send, `Alt-@` to toggle terminal).
219+
- `<M-CR>` in Neovim / `<ESC><CR>` in Vim: Sends line/selection to FSI.
220+
- `<M-@>` in Neovim / `<ESC>@` in Vim: Toggles FSI window.
221+
* `vim-fsharp`: Same as in [fsharp/vim-fsharp](https://github.com/fsharp/vim-fsharp#fsharp-interactive). Note that `<leader>` is mapped to backslash by default. See [`:help mapleader`](http://vimdoc.sourceforge.net/htmldoc/map.html#mapleader).
222+
- `<leader>i` : Sends line/selecion to FSI.
223+
- `<leader>e` : Toggles FSI window.
224+
* `custom`: You must set both `g:fsharp#fsi_keymap_send` and `g:fsharp#fsi_keymap_toggle` by yourself.
225+
- `g:fsharp#fsi_keymap_send` : Sends line/selection to FSI.
226+
- `g:fsharp#fsi_keymap_toggle` : Toggles FSI window.
227+
* `none`: Disables mapping.
228+
229+
~~~.vim
230+
" custom mapping example
231+
let g:fsharp#fsi_keymap = "custom"
232+
let g:fsharp#fsi_keymap_send = "<C-e>"
233+
let g:fsharp#fsi_keymap_toggle = "<C-@>"
234+
~~~
235+
152236
### Advanced Tips
153237

154238
#### Show tooltips on CursorHold
@@ -159,7 +243,7 @@ If you are using neovim 0.4.0 or later, floating windows will be used for toolti
159243
if has('nvim') && exists('*nvim_open_win')
160244
augroup FSharpShowTooltip
161245
autocmd!
162-
autocmd CursorHold *.fs call fsharp#showTooltip()
246+
autocmd CursorHold *.fs,*.fsi,*.fsx call fsharp#showTooltip()
163247
augroup END
164248
endif
165249
~~~

autoload/fsharp.vim

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,12 @@ function! s:findWorkspace(dir, cont)
130130
if workspace.Type == 'none'
131131
let workspace = found
132132
elseif found.Type == 'solution'
133-
if workspace.Type == 'project' then
133+
if workspace.Type == 'project'
134134
let workspace = found
135135
else
136136
let curLen = len(workspace.Data.Items)
137137
let newLen = len(found.Data.Items)
138-
if newLen > curLen then
138+
if newLen > curLen
139139
let workspace = found
140140
endif
141141
endif
@@ -264,6 +264,157 @@ function! fsharp#updateFSAC(...)
264264
call s:download(branch)
265265
endfunction
266266

267+
let s:fsi_buffer = 0
268+
let s:fsi_job = 0
269+
let s:fsi_width = 0
270+
let s:fsi_height = 0
271+
272+
function! s:win_gotoid_safe(winid)
273+
function! s:vimReturnFocus(window)
274+
call win_gotoid(a:window)
275+
redraw!
276+
endfunction
277+
if has('nvim')
278+
call win_gotoid(a:winid)
279+
else
280+
call timer_start(1, { -> s:vimReturnFocus(a:winid) })
281+
endif
282+
endfunction
283+
284+
function! fsharp#openFsi(returnFocus)
285+
if bufwinid(s:fsi_buffer) <= 0
286+
" Neovim
287+
if exists('*termopen') || exists('*term_start')
288+
let current_win = win_getid()
289+
execute g:fsharp#fsi_window_command
290+
if s:fsi_width > 0 | execute 'vertical resize' s:fsi_width | endif
291+
if s:fsi_height > 0 | execute 'resize' s:fsi_height | endif
292+
" if window is closed but FSI is still alive then reuse it
293+
if s:fsi_buffer != 0 && bufexists(str2nr(s:fsi_buffer))
294+
exec 'b' s:fsi_buffer
295+
normal G
296+
if !has('nvim') && mode() == 'n' | execute "normal A" | endif
297+
if a:returnFocus | call s:win_gotoid_safe(current_win) | endif
298+
" open FSI: Neovim
299+
elseif has('nvim')
300+
let s:fsi_job = termopen(g:fsharp#fsi_command)
301+
if s:fsi_job > 0
302+
let s:fsi_buffer = bufnr("%")
303+
else
304+
close
305+
echom "[FSAC] Failed to open FSI."
306+
return -1
307+
endif
308+
" open FSI: Vim
309+
else
310+
let options = {
311+
\ "term_name": "F# Interactive",
312+
\ "curwin": 1,
313+
\ "term_finish": "close"
314+
\ }
315+
let s:fsi_buffer = term_start(g:fsharp#fsi_command, options)
316+
if s:fsi_buffer != 0
317+
if exists('*term_setkill') | call term_setkill(s:fsi_buffer, "term") | endif
318+
let s:fsi_job = term_getjob(s:fsi_buffer)
319+
else
320+
close
321+
echom "[FSAC] Failed to open FSI."
322+
return -1
323+
endif
324+
endif
325+
setlocal bufhidden=hide
326+
normal G
327+
if a:returnFocus | call s:win_gotoid_safe(current_win) | endif
328+
return s:fsi_buffer
329+
else
330+
echom "[FSAC] Your Vim does not support terminal".
331+
return 0
332+
endif
333+
endif
334+
return s:fsi_buffer
335+
endfunction
336+
337+
function! fsharp#toggleFsi()
338+
let fsiWindowId = bufwinid(s:fsi_buffer)
339+
if fsiWindowId > 0
340+
let current_win = win_getid()
341+
call win_gotoid(fsiWindowId)
342+
let s:fsi_width = winwidth('%')
343+
let s:fsi_height = winheight('%')
344+
close
345+
call win_gotoid(current_win)
346+
else
347+
call fsharp#openFsi(0)
348+
endif
349+
endfunction
350+
351+
function! fsharp#quitFsi()
352+
if s:fsi_buffer != 0 && bufexists(str2nr(s:fsi_buffer))
353+
if has('nvim')
354+
let winid = bufwinid(s:fsi_buffer)
355+
if winid > 0 | execute "close " . winid | endif
356+
call jobstop(s:fsi_job)
357+
else
358+
call job_stop(s:fsi_job, "term")
359+
endif
360+
let s:fsi_buffer = 0
361+
let s:fsi_job = 0
362+
endif
363+
endfunction
364+
365+
function! fsharp#resetFsi()
366+
call fsharp#quitFsi()
367+
return fsharp#openFsi(1)
368+
endfunction
369+
370+
function! fsharp#sendFsi(text)
371+
if fsharp#openFsi(!g:fsharp#fsi_focus_on_send) > 0
372+
" Neovim
373+
if has('nvim')
374+
call chansend(s:fsi_job, a:text . ";;". "\n")
375+
" Vim 8
376+
else
377+
call term_sendkeys(s:fsi_buffer, a:text . ";;" . "\<cr>")
378+
call term_wait(s:fsi_buffer)
379+
endif
380+
endif
381+
endfunction
382+
383+
" https://stackoverflow.com/a/6271254
384+
function! s:get_visual_selection()
385+
let [line_start, column_start] = getpos("'<")[1:2]
386+
let [line_end, column_end] = getpos("'>")[1:2]
387+
let lines = getline(line_start, line_end)
388+
if len(lines) == 0
389+
return ''
390+
endif
391+
let lines[-1] = lines[-1][: column_end - (&selection == 'inclusive' ? 1 : 2)]
392+
let lines[0] = lines[0][column_start - 1:]
393+
return lines
394+
endfunction
395+
396+
function! s:get_complete_buffer()
397+
return join(getline(1, '$'), "\n")
398+
endfunction
399+
400+
function! fsharp#sendSelectionToFsi() range
401+
let lines = s:get_visual_selection()
402+
exec 'normal' len(lines) . 'j'
403+
let text = join(lines, "\n")
404+
return fsharp#sendFsi(text)
405+
endfunction
406+
407+
function! fsharp#sendLineToFsi()
408+
let text = getline('.')
409+
exec 'normal j'
410+
return fsharp#sendFsi(text)
411+
endfunction
412+
413+
function! fsharp#sendAllToFsi()
414+
let text = s:get_complete_buffer()
415+
return fsharp#sendFsi(text)
416+
endfunction
417+
267418
let &cpo = s:cpo_save
268419
unlet s:cpo_save
269420

0 commit comments

Comments
 (0)