diff --git a/README.mkd b/README.mkd index 2fdfb12..8226b7c 100644 --- a/README.mkd +++ b/README.mkd @@ -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 @@ -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 @@ -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 @@ -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. @@ -103,20 +108,49 @@ 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 +` - - 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 ` + - 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 @@ -124,31 +158,81 @@ Refer to [LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neov 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). + - `` in Neovim / `` in Vim: Sends line/selection to FSI. + - `` in Neovim / `@` in Vim: Toggles FSI window. +* `vim-fsharp`: Same as in [fsharp/vim-fsharp](https://github.com/fsharp/vim-fsharp#fsharp-interactive). Note that `` is mapped to backslash by default. See [`:help mapleader`](http://vimdoc.sourceforge.net/htmldoc/map.html#mapleader). + - `i` : Sends line/selecion to FSI. + - `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 = "" +let g:fsharp#fsi_keymap_toggle = "" +~~~ + ### Advanced Tips #### 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 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 ~~~ diff --git a/autoload/fsharp.vim b/autoload/fsharp.vim index d232ae1..28d30a6 100644 --- a/autoload/fsharp.vim +++ b/autoload/fsharp.vim @@ -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 @@ -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 . ";;" . "\") + 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 diff --git a/ftplugin/fsharp.vim b/ftplugin/fsharp.vim index 7be2d36..5320801 100644 --- a/ftplugin/fsharp.vim +++ b/ftplugin/fsharp.vim @@ -26,6 +26,18 @@ endif if !exists('g:fsharp#show_signature_on_cursor_move') let g:fsharp#show_signature_on_cursor_move = 1 endif +if !exists('g:fsharp#fsi_command') + let g:fsharp#fsi_command = "dotnet fsi" +endif +if !exists('g:fsharp#fsi_keymap') + let g:fsharp#fsi_keymap = "vscode" +endif +if !exists('g:fsharp#fsi_window_command') + let g:fsharp#fsi_window_command = "botright 10new" +endif +if !exists('g:fsharp#fsi_focus_on_send') + let g:fsharp#fsi_focus_on_send = 0 +endif let s:cpo_save = &cpo set cpo&vim @@ -49,7 +61,8 @@ if g:fsharp#automatic_workspace_init endif augroup FSharpLC_fs - autocmd! CursorMoved *.fs call fsharp#OnCursorMove() + autocmd! + autocmd CursorMoved *.fs,*.fsi,*.fsx call fsharp#OnCursorMove() augroup END com! -buffer FSharpLoadWorkspaceAuto call fsharp#loadWorkspaceAuto() @@ -57,6 +70,39 @@ com! -buffer FSharpReloadWorkspace call fsharp#reloadProjects() com! -buffer -nargs=* FSharpUpdateFSAC call fsharp#updateFSAC() com! -buffer -nargs=* -complete=file FSharpParseProject call fsharp#loadProject() +com! -buffer -nargs=1 FsiEval call fsharp#sendFsi() +com! -buffer FsiEvalBuffer call fsharp#sendAllToFsi() +com! -buffer FsiReset call fsharp#resetFsi() +com! -buffer FsiShow call fsharp#openFsi() + +if g:fsharp#fsi_keymap == "vscode" + if has('nvim') + let g:fsharp#fsi_keymap_send = "" + let g:fsharp#fsi_keymap_toggle = "" + else + let g:fsharp#fsi_keymap_send = "" + let g:fsharp#fsi_keymap_toggle = "@" + endif +elseif g:fsharp#fsi_keymap == "vim-fsharp" + let g:fsharp#fsi_keymap_send = "i" + let g:fsharp#fsi_keymap_toggle = "e" +elseif g:fsharp#fsi_keymap == "custom" + let g:fsharp#fsi_keymap = "none" + if !exists('g:fsharp#fsi_keymap_send') + echoerr "g:fsharp#fsi_keymap_send is not set" + elseif !exists('g:fsharp#fsi_keymap_toggle') + echoerr "g:fsharp#fsi_keymap_toggle is not set" + else + let g:fsharp#fsi_keymap = "custom" + endif +endif +if g:fsharp#fsi_keymap != "none" + execute "vnoremap " g:fsharp#fsi_keymap_send ":call fsharp#sendSelectionToFsi()" + execute "nnoremap " g:fsharp#fsi_keymap_send ":call fsharp#sendLineToFsi()" + execute "nnoremap " g:fsharp#fsi_keymap_toggle ":call fsharp#toggleFsi()" + execute "tnoremap " g:fsharp#fsi_keymap_toggle ":call fsharp#toggleFsi()" +endif + let &cpo = s:cpo_save " vim: sw=4 et sts=4