Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

F# Interactive Integration #10

Merged
merged 8 commits into from
Oct 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 94 additions & 10 deletions README.mkd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

**F# support for Vim/Neovim**

![ionide-vim](https://i.imgur.com/3RLcJw6.gif)

_Part of the [Ionide](http://ionide.io) plugin suite._

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

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

## Getting Started

Expand Down Expand Up @@ -92,6 +95,8 @@ let g:LanguageClient_serverCommands = {

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

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

## Usage

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

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

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

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

#### `:FSharpUpdateFSAC`
- Download the latest build of FsAutoComplete to be used with Ionide-vim.
- Downloads the latest build of FsAutoComplete to be used with Ionide-vim.

### Working with F# Interactive

Ionide-vim has an integration with F# Interactive.

FSI is displayed using the builtin `:terminal` feature introduced in Vim 8 / Neovim and can be used like in VSCode.

#### `:FsiShow`
- Shows F# Interactive windows.

#### `:FsiEval <expr>`
- Evaluates given expression in FSI.

#### `:FsiEvalBuffer`
- Sends the content of current file to FSI.

#### `:FsiReset`
- Resets the current FSI session.

#### `Alt-Enter`
- When in normal mode, sends the current line to FSI.
- When in visual mode, sends the selection to FSI.
- 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.

#### `Alt-@`
- Toggles FSI window. FSI windows shown in different tabpages share the same FSI session.
- When opened, the cursor automatically focuses to the FSI window (unlike in `Alt-Enter` by default).

You can customize the location of FSI, key mappings, etc. See [the documentation below](#f-interactive-settings).

### Settings

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

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

#### Enable/disable automatic calling of `:FSharpLoadWorkspaceAuto` on opening F# files (default: 1)
#### Workspace Settings

##### Enable/disable automatic calling of `:FSharpLoadWorkspaceAuto` on opening F# files (default: 1)

~~~.vim
let g:fsharp#automatic_workspace_init = 1 " 0 to disable.
~~~

#### Set the deep level of directory hierarchy when searching for sln/fsprojs (default: 2)
##### Set the deep level of directory hierarchy when searching for sln/fsprojs (default: 2)

~~~.vim
let g:fsharp#workspace_mode_peek_deep_level = 2

~~~

#### Enable/disable automatic calling of `:FSharpReloadWorkspace` on saving `fsproj` (default: 1)
##### Enable/disable automatic calling of `:FSharpReloadWorkspace` on saving `fsproj` (default: 1)

~~~.vim
let g:fsharp#automatic_reload_workspace = 1 " 0 to disable.
~~~

#### Show type signature at cursor position (default: 1)
#### Editor Settings

##### Show type signature at cursor position (default: 1)

~~~.vim
let g:fsharp#show_signature_on_cursor_move = 1 " 0 to disable.
~~~

#### F# Interactive Settings

##### Change the F# Interactive command to be used within Ionide-vim (default: `dotnet fsi`)

~~~.vim
let g:fsharp#fsi_command = "fsharpi"
~~~

##### Customize how FSI window is opened (default: `botright 10new`)

It must create a new empty window and then focus to it.

See [`:help opening-window`](http://vimdoc.sourceforge.net/htmldoc/windows.html#opening-window) for details.

~~~.vim
let g:fsharp#fsi_window_command = "botright vnew"
~~~

##### Set if sending line/selection to FSI shoule make the cursor focus to FSI window (default: `0`)

If you are using Vim, you might want to enable this to see the result without inputting something.

~~~.vim
let g:fsharp#fsi_focus_on_send = 1 " 0 to not to focus.
~~~

##### Change the key mappings (default: `vscode`)

* `vscode`: Default. Same as in Ionide-VSCode (`Alt-Enter` to send, `Alt-@` to toggle terminal).
- `<M-CR>` in Neovim / `<ESC><CR>` in Vim: Sends line/selection to FSI.
- `<M-@>` in Neovim / `<ESC>@` in Vim: Toggles FSI window.
* `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).
- `<leader>i` : Sends line/selecion to FSI.
- `<leader>e` : Toggles FSI window.
* `custom`: You must set both `g:fsharp#fsi_keymap_send` and `g:fsharp#fsi_keymap_toggle` by yourself.
- `g:fsharp#fsi_keymap_send` : Sends line/selection to FSI.
- `g:fsharp#fsi_keymap_toggle` : Toggles FSI window.
* `none`: Disables mapping.

~~~.vim
" custom mapping example
let g:fsharp#fsi_keymap = "custom"
let g:fsharp#fsi_keymap_send = "<C-e>"
let g:fsharp#fsi_keymap_toggle = "<C-@>"
~~~

### Advanced Tips

#### Show tooltips on CursorHold
Expand All @@ -159,7 +243,7 @@ If you are using neovim 0.4.0 or later, floating windows will be used for toolti
if has('nvim') && exists('*nvim_open_win')
augroup FSharpShowTooltip
autocmd!
autocmd CursorHold *.fs call fsharp#showTooltip()
autocmd CursorHold *.fs,*.fsi,*.fsx call fsharp#showTooltip()
augroup END
endif
~~~
Expand Down
155 changes: 153 additions & 2 deletions autoload/fsharp.vim
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,12 @@ function! s:findWorkspace(dir, cont)
if workspace.Type == 'none'
let workspace = found
elseif found.Type == 'solution'
if workspace.Type == 'project' then
if workspace.Type == 'project'
let workspace = found
else
let curLen = len(workspace.Data.Items)
let newLen = len(found.Data.Items)
if newLen > curLen then
if newLen > curLen
let workspace = found
endif
endif
Expand Down Expand Up @@ -264,6 +264,157 @@ function! fsharp#updateFSAC(...)
call s:download(branch)
endfunction

let s:fsi_buffer = 0
let s:fsi_job = 0
let s:fsi_width = 0
let s:fsi_height = 0

function! s:win_gotoid_safe(winid)
function! s:vimReturnFocus(window)
call win_gotoid(a:window)
redraw!
endfunction
if has('nvim')
call win_gotoid(a:winid)
else
call timer_start(1, { -> s:vimReturnFocus(a:winid) })
endif
endfunction

function! fsharp#openFsi(returnFocus)
if bufwinid(s:fsi_buffer) <= 0
" Neovim
if exists('*termopen') || exists('*term_start')
let current_win = win_getid()
execute g:fsharp#fsi_window_command
if s:fsi_width > 0 | execute 'vertical resize' s:fsi_width | endif
if s:fsi_height > 0 | execute 'resize' s:fsi_height | endif
" if window is closed but FSI is still alive then reuse it
if s:fsi_buffer != 0 && bufexists(str2nr(s:fsi_buffer))
exec 'b' s:fsi_buffer
normal G
if !has('nvim') && mode() == 'n' | execute "normal A" | endif
if a:returnFocus | call s:win_gotoid_safe(current_win) | endif
" open FSI: Neovim
elseif has('nvim')
let s:fsi_job = termopen(g:fsharp#fsi_command)
if s:fsi_job > 0
let s:fsi_buffer = bufnr("%")
else
close
echom "[FSAC] Failed to open FSI."
return -1
endif
" open FSI: Vim
else
let options = {
\ "term_name": "F# Interactive",
\ "curwin": 1,
\ "term_finish": "close"
\ }
let s:fsi_buffer = term_start(g:fsharp#fsi_command, options)
if s:fsi_buffer != 0
if exists('*term_setkill') | call term_setkill(s:fsi_buffer, "term") | endif
let s:fsi_job = term_getjob(s:fsi_buffer)
else
close
echom "[FSAC] Failed to open FSI."
return -1
endif
endif
setlocal bufhidden=hide
normal G
if a:returnFocus | call s:win_gotoid_safe(current_win) | endif
return s:fsi_buffer
else
echom "[FSAC] Your Vim does not support terminal".
return 0
endif
endif
return s:fsi_buffer
endfunction

function! fsharp#toggleFsi()
let fsiWindowId = bufwinid(s:fsi_buffer)
if fsiWindowId > 0
let current_win = win_getid()
call win_gotoid(fsiWindowId)
let s:fsi_width = winwidth('%')
let s:fsi_height = winheight('%')
close
call win_gotoid(current_win)
else
call fsharp#openFsi(0)
endif
endfunction

function! fsharp#quitFsi()
if s:fsi_buffer != 0 && bufexists(str2nr(s:fsi_buffer))
if has('nvim')
let winid = bufwinid(s:fsi_buffer)
if winid > 0 | execute "close " . winid | endif
call jobstop(s:fsi_job)
else
call job_stop(s:fsi_job, "term")
endif
let s:fsi_buffer = 0
let s:fsi_job = 0
endif
endfunction

function! fsharp#resetFsi()
call fsharp#quitFsi()
return fsharp#openFsi(1)
endfunction

function! fsharp#sendFsi(text)
if fsharp#openFsi(!g:fsharp#fsi_focus_on_send) > 0
" Neovim
if has('nvim')
call chansend(s:fsi_job, a:text . ";;". "\n")
" Vim 8
else
call term_sendkeys(s:fsi_buffer, a:text . ";;" . "\<cr>")
call term_wait(s:fsi_buffer)
endif
endif
endfunction

" https://stackoverflow.com/a/6271254
function! s:get_visual_selection()
let [line_start, column_start] = getpos("'<")[1:2]
let [line_end, column_end] = getpos("'>")[1:2]
let lines = getline(line_start, line_end)
if len(lines) == 0
return ''
endif
let lines[-1] = lines[-1][: column_end - (&selection == 'inclusive' ? 1 : 2)]
let lines[0] = lines[0][column_start - 1:]
return lines
endfunction

function! s:get_complete_buffer()
return join(getline(1, '$'), "\n")
endfunction

function! fsharp#sendSelectionToFsi() range
let lines = s:get_visual_selection()
exec 'normal' len(lines) . 'j'
let text = join(lines, "\n")
return fsharp#sendFsi(text)
endfunction

function! fsharp#sendLineToFsi()
let text = getline('.')
exec 'normal j'
return fsharp#sendFsi(text)
endfunction

function! fsharp#sendAllToFsi()
let text = s:get_complete_buffer()
return fsharp#sendFsi(text)
endfunction

let &cpo = s:cpo_save
unlet s:cpo_save

Expand Down
Loading