From 885c1c9f151091e13d714ed0a48e4ff622bcbbf3 Mon Sep 17 00:00:00 2001 From: cannorin Date: Sat, 13 Jul 2019 21:57:09 +0900 Subject: [PATCH] Rewrite for LSP --- .gitignore | 3 + Makefile | 9 +- README.mkd | 211 +++---------- autoload/fsharp.vim | 223 ++++++++++++++ autoload/fsharpbinding/python.vim | 489 ------------------------------ ftdetect/fsharp.vim | 1 + ftplugin/fsharp.vim | 187 ++---------- ftplugin/fsharp_project.vim | 18 ++ ftplugin/fsharpvim.py | 301 ------------------ ftplugin/fsi.py | 122 -------- ftplugin/hidewin.py | 9 - ftplugin/pyvim.py | 5 - install.fsx | 17 +- syntax_checkers/fsharp/syntax.vim | 24 -- test.fsx | 20 -- 15 files changed, 329 insertions(+), 1310 deletions(-) create mode 100644 autoload/fsharp.vim delete mode 100644 autoload/fsharpbinding/python.vim create mode 100644 ftplugin/fsharp_project.vim delete mode 100644 ftplugin/fsharpvim.py delete mode 100644 ftplugin/fsi.py delete mode 100644 ftplugin/hidewin.py delete mode 100644 ftplugin/pyvim.py delete mode 100644 syntax_checkers/fsharp/syntax.vim delete mode 100644 test.fsx diff --git a/.gitignore b/.gitignore index ef8852e..a9dcc12 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ *.pyc tags paket.exe +.paket ftplugin/bin/ packages/ FsAutoComplete/* +fsac/ +fsac.zip diff --git a/Makefile b/Makefile index 9a4fafc..3a93acb 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,17 @@ # Directories -bin_d = $(abspath ftplugin/bin) +bin_d = $(abspath fsac) # Installation paths. #dest_root = $(HOME)/.vim/bundle/vim-fsharp/ #dest_bin = $(dest_root)/ftplugin/bin/ ac_exe = $(bin_d)/fsautocomplete.exe -ac_archive = fsautocomplete.zip -ac_version = 0.34.0 +ac_archive = fsautocomplete.netcore.zip +ac_version = master ac_url = https://github.com/fsharp/FSharp.AutoComplete/releases/download/$(ac_version)/$(ac_archive) - +ac_url = https://ci.appveyor.com/api/projects/fsautocomplete/fsautocomplete/artifacts/bin/pkgs/$(ac_archive)?branch=$(ac_version) + git_url = https://github.com/fsharp/FsAutoComplete.git # ---------------------------------------------------------------------------- diff --git a/README.mkd b/README.mkd index beb2ca8..73c6b1e 100644 --- a/README.mkd +++ b/README.mkd @@ -1,14 +1,22 @@ -## F# support for Vim (vim-fsharp) +## F# support for Vim through LSP (vim-fsharp-languageclient) -Syntax and indent files have been copied from [fsharp-vim](http://github.com/kongo2002/fsharp-vim) with kind permissions from [kongo2002](https://github.com/kongo2002). +This is a modified version of [fsharp/vim-fsharp](https://github.com/fsharp/vim-fsharp) which uses LSP-mode of [FsAutoComplete](https://github.com/fsharp/FsAutoComplete) as a backend, and is powered by [autozimu/LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim). -> Requires vim 7.3 or higher compiled with python 2 or 3 support. +The original description is as follows: -This was adapted from http://github.com/timrobinson/fsharp-vim. The current aim is to provide a good experience for fsx scripting. On opening an fs or fsi file any project file found in the same directory will be parsed. Multiple projects are supported. +> Syntax and indent files have been copied from [fsharp-vim](http://github.com/kongo2002/fsharp-vim) with kind permissions from [kongo2002](https://github.com/kongo2002). +> +> > Requires vim 7.3 or higher compiled with python 2 or 3 support. +> +> This was adapted from http://github.com/timrobinson/fsharp-vim. The current aim is to provide a good experience for fsx scripting. On opening an fs or fsi file any project file found in the same directory will be parsed. Multiple projects are supported. + +Note that this plugin does not require python support. Also, [LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim) requires neovim or vim 8.0+. ### Installing -vim-fsharp requires mono and fsharp installed. +vim-fsharp-languageclient requires .NET Core Runtime installed. Also it depends on [LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim), so make sure it's installed beforehand. + +I have only tested vim-plug and _install.sh_(_install.cmd_ should also work). #### OSX and linux @@ -16,14 +24,14 @@ vim-fsharp requires mono and fsharp installed. 1. Install [pathogen][pathogen] and [syntastic][syntastic] -2. Clone vim-fsharp into your bundle directory. +2. Clone vim-fsharp-languageclient into your bundle directory. 2. Run *make* inside the vim directory. This downloads the auto completion server. ##### Installing with [vim-plug][vim-plug] ~~~.vim -Plug 'fsharp/vim-fsharp', { +Plug 'cannorin/vim-fsharp-languageclient', { \ 'for': 'fsharp', \ 'do': 'make fsautocomplete', \} @@ -33,7 +41,7 @@ Plug 'fsharp/vim-fsharp', { By VimL way: ~~~.vim -NeoBundle 'fsharp/vim-fsharp', { +NeoBundle 'cannorin/vim-fsharp-languageclient', { \ 'description': 'F# support for Vim', \ 'lazy': 1, \ 'autoload': {'filetypes': 'fsharp'}, @@ -48,7 +56,7 @@ By using TOML configuration: ~~~.toml [[plugins]] description = 'F# support for Vim' -repository = 'fsharp/vim-fsharp' +repository = 'cannorin/vim-fsharp-languageclient' lazy = 1 filetypes = 'fsharp' build_commands = ['curl', 'make', 'mozroots', 'touch', 'unzip'] @@ -58,196 +66,57 @@ build_commands = ['curl', 'make', 'mozroots', 'touch', 'unzip'] #### Windows -1. Install [pathogen][pathogen] and [syntastic][syntastic] +1. Run _install.cmd_ -2. Run _install.cmd_ +### Setting up the language client -#### Syntastic +Once you installed, add the following to somewhere in your `.vimrc`: -vim-fsharp utilizes the [syntastic][syntastic] plugin in order to -supply interactive syntax and type checking. You may want to install that plugin -in order to get all of the fsharpbindings functionality. +```vim +let g:LanguageClient_serverCommands = { + \ 'fsharp': g:fsharp#languageserver_command + \ } +``` -Moreover you benefit from additional [syntastic][syntastic] features like -optional integration in your status bar (i.e. [vim-airline][airline]). - -All you have to do is to install [syntastic][syntastic] in your vim runtime path. +This will configure FSAC to be used from LanguageClient-neovim. ### Usage Opening either `*.fs`, `*.fsi` or `*.fsx` files should trigger syntax highlighting and other depending runtime files as well. -Omnicomplete triggers the fsharp autocomplete process. (suggestion: install [supertab](https://github.com/ervandew/supertab)) - ### Commands -##### General -* `:make` Calls xbuild on the fsproj for the current file (if any). -* `:FSharpParseProject` Reparses all the project files and dependencies (this is done automatically when opening a .fs or .fsi file). -* `:FSharpBuildProject` Calls xbuild on the fsproj for the current file (if any). Can also take a path to the proj file to build. -* `:FSharpRunProject` Runs the project for the current file (if any). -* `:FSharpRunTests` If `g:fsharp_test_runner` is set it will build the current project and run any tests. (Currently only tested with nunit-console.exe) -* `:FSharpToggleHelptext` toggles g:fsharp_completion_helptext. (See below for details) -* `leader` Echoes the type of the expression currently pointed to by the cursor. -* `leader` Echoes the help info (type and comments) of the expression currently pointed to by the cursor. -* `leader` _go to declaration_ in current window. -* `leader` Takes you back from where _go to declaration_ was triggered. Experimental. - -##### FSharp Interactive -* `:FsiEval` Evaluates an fsharp expression in the fsi -* `leader` Same as FsiEval but from a vim command line -* `:FsiEvalBuffer` Evaluates the entire buffer in the fsi -* `:FsiReset` Resets the current fsharp interactive -* `:FsiRead` Outputs any lines written by the fsi but not yet output as vim messages -* `:FsiClear` Deletes all text from the fsi output buffer but doesn't reset the fsi session. -* `:FsiShow` Opens the _fsi-out_ buffer in a split window. -* `Alt-Enter` Send either the current selection or the current line to the fsharp interactive and echoes the output the first line of the output. All output will be written to the _fsi-out_ buffer. -* `leader` Same as Alt-Enter - -### On-the-fly syntax checking - -> Interactive syntax/type checking requires vim 7.4 or higher and [syntastic][syntastic] - -By default your F# files will be syntax/type checked on every open/save of a vim buffer as well as after 500ms of inactivity in Normal mode. -In case you would prefer not to have you errors checked continuously add the following to your vimrc: - -~~~.vim -let g:fsharp_only_check_errors_on_write = 1 -~~~ +Refer to [LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim) for features provided via Language Server Protocol. -In case you prefer to disable the syntax checker, add the following to your vimrc: +To be added as requested for F#-specific features. -~~~.vim -let g:syntastic_fsharp_checkers = [''] -~~~ +##### General +* :FSharpLoadWorkspaceAuto` Guess a workspace (`sln` or `fsproj`) and then load it. Equivalent to `FSharp.workspaceMode = sln` in Ionide. Automatically called when you open F# files. +* `:FSharpParseProject +` Load specified projects (`fsproj` or `sln`). +* `:FSharpReloadWorkspace` Reload all the projects currently loaded. Automatically called when you edit and save `.fsproj` files. ### Settings -You can enable *debug-mode* in order to inspect the fsautocomplete behavior by -setting the global vim variable `g:fsharpbinding_debug` to a non-zero value: - -~~~.vim -let g:fsharpbinding_debug = 1 -~~~ - -This will create two log files `log.txt` and `log2.txt` in your temporary folder -(i.e. `/tmp/`). - -Override the default F# interactive binary - -~~~.vim -let g:fsharp_interactive_bin = '/path/to/fsi' -~~~ - -You can set the msbuild/xbuild path. - -~~~.vim -let g:fsharp_xbuild_path = "/path/to/xbuild/or/msbuild" -~~~ - -This setting needs to point to a suitable test runner (such as nunit-console.exe) - -~~~.vim -let g:fsharp_test_runner = "/path/to/test/runner" -~~~ - -This enables the helptext to be displayed during auto completion. Turn off if completion is too slow. - -~~~.vim -let g:fsharp_completion_helptext = 1 -~~~ - -Show comments, in addition to type signature, when using Omni completion (default=0). - -~~~.vim -let g:fsharp_helptext_comments = 1 -~~~ - -If you find the default bindings unsuitable then it is possible to turn them off. - -~~~.vim -let g:fsharp_map_keys = 0 -~~~ - -It is also possible to configure them to provide a more customised experience. - -Override the default prefix of `` to the keys `cp` - -~~~.vim -let g:fsharp_map_prefix = 'cp' -~~~ - -Override the default mapping to send the current like to fsharp interactive - -~~~.vim -let g:fsharp_map_fsisendline = 'p' -~~~ - -Override the default mapping to send the current selection to fsharp interactive - -~~~.vim -let g:fsharp_map_fsisendsel = 'p' -~~~ - -Override the default mapping to go to declaration in the current window +Refer to [LanguageClient-neovim](https://github.com/autozimu/LanguageClient-neovim) for features provided via Language Server Protocol. -~~~.vim -let g:fsharp_map_gotodecl = 'g' -~~~ - -Override the default mapping to go back to where go to declaration was triggered -~~~.vim -let g:fsharp_map_gobackfromdecl = 'b' -~~~ +To be added as requested for F#-specific features. -Override the default mapping to evaluate an fsharp expression in the fsi +Show the type signature at the cursor position (default: 1). ~~~.vim -let g:fsharp_map_fsiinput = 'i' +let g:fsharp#show_signature_on_cursor_move = 1 " 0 to disable. ~~~ -Automatically open the result of an FsiEval (fsi-out buffer) in a vsplit window +Enable/disable automatic calling of `:FSharpLoadWorkspaceAuto`. (default: 1) ~~~.vim -let g:fsharp_fsi_show_auto_open = 1 +let g:fsharp#automatic_workspace_init = 1 " 0 to disable. ~~~ - -### Troubleshooting - -> I get syntax highlighting but not error checking and commands like :FsiEval are not found - -Type +Enable/disable automatic calling of `:FSharpReloadWorkspace`. (default: 1) ~~~.vim -:SyntasticInfo -~~~ - -The "available" and "currently enabled" checkers should be listed as "syntax". If these entries are blank, then -the syntax checker has not been loaded properly. One cause can be that your vim is compiled without Python support; type - -~~~ -:echo has('python') -:echo has('python3') +let g:fsharp#automatic_reload_workspace = 1 " 0 to disable. ~~~ -to check this. - -If both of this commands return 0 and you're on Debian 8, you may need to install the package 'vim-python-jedi' instead of 'vim' to have Python support. - -[syntastic]: https://github.com/scrooloose/syntastic -[airline]: https://github.com/bling/vim-airline -[pathogen]: https://github.com/tpope/vim-pathogen -[vim-plug]: https://github.com/junegunn/vim-plug -[NeoBundle]: https://github.com/Shougo/neobundle.vim - -NB: if you in the past installed `fsharpbinding-vim` you need to remove this from the `bundles` directory. They don't play nicely together and `fsharpbinding-vim` is no longer developed. - -Maintainers ------------ - -Tha maintainers of this repository appointed by the F# Core Engineering Group are: - - - [Robin Neatherway](https://github.com/rneatherway), [Steffen Forkmann](http://github.com/forki), [Karl Nilsson](http://github.com/kjnilsson), [Dave Thomas](http://github.com/7sharp9) and [Guillermo López-Anglada](http://github.com/guillermooo) - - The primary maintainer for this repository is [Karl Nilsson](http://github.com/kjnilsson) diff --git a/autoload/fsharp.vim b/autoload/fsharp.vim new file mode 100644 index 0000000..8bcaeab --- /dev/null +++ b/autoload/fsharp.vim @@ -0,0 +1,223 @@ +" Vim autoload functions + +if exists('g:loaded_autoload_fsharp') + finish +endif +let g:loaded_autoload_fsharp = 1 + +let s:cpo_save = &cpo +set cpo&vim + +function! s:PlainNotification(content) + return { 'Content': a:content } +endfunction + +function! s:TextDocumentIdentifier(path) + let usr_ss_opt = &shellslash + set shellslash + let uri = fnamemodify(a:path, ":p") + if uri[0] == "/" + let uri = "file://" . uri + else + let uri = "file:///" . uri + endif + let &shellslash = usr_ss_opt + return { 'Uri': uri } +endfunction + +function! s:Position(line, character) + return { 'Line': a:line, 'Character': a:character } +endfunction + +function! s:TextDocumentPositionParams(documentUri, line, character) + return { + \ 'TextDocument': s:TextDocumentIdentifier(a:documentUri), + \ 'Position': s:Position(a:line, a:character) + \ } +endfunction + +function! s:DocumentationForSymbolRequest(xmlSig, assembly) + return { + \ 'XmlSig': a:xmlSig, + \ 'Assembly': a:assembly + \ } +endfunction + +function! s:ProjectParms(projectUri) + return { 'Project': s:TextDocumentIdentifier(a:projectUri) } +endfunction + +function! s:WorkspacePeekRequest(directory, deep, excludedDirs) + return { + \ 'Directory': fnamemodify(a:directory, ":p"), + \ 'Deep': a:deep, + \ 'ExcludedDirs': a:excludedDirs + \ } +endfunction + +function! s:WorkspaceLoadParms(files) + let prm = [] + for file in a:files + call add(prm, s:TextDocumentIdentifier(file)) + endfor + return { 'TextDocuments': prm } +endfunction + +function! s:FsdnRequest(query) + return { 'Query': a:query } +endfunction + +function! s:call(method, params) + let result = [] + call LanguageClient#Call(a:method, a:params, result) + while len(result) == 0 + sleep 10m + endwhile + let res = result[0] + return res +endfunction + +function! s:signature(filePath, line, character) + return s:call('fsharp/signature', s:TextDocumentPositionParams(a:filePath, a:line, a:character)) +endfunction +function! s:signatureData(filePath, line, character) + return s:call('fsharp/signatureData', s:TextDocumentPositionParams(a:filePath, a:line, a:character)) +endfunction +function! s:lineLens(projectPath) + return s:call('fsharp/lineLens', s:ProjectParms(a:projectPath)) +endfunction +function! s:compilerLocation() + return s:call('fsharp/compilerLocation', {}) +endfunction +function! s:compile(projectPath) + return s:call('fsharp/compile', s:ProjectParms(a:projectPath)) +endfunction +function! s:workspacePeek(directory, depth, excludedDirs) + return s:call('fsharp/workspacePeek', s:WorkspacePeekRequest(a:directory, a:depth, a:excludedDirs)) +endfunction +function! s:workspaceLoad(files) + return s:call('fsharp/workspaceLoad', s:WorkspaceLoadParms(a:files)) +endfunction +function! s:project(projectPath) + return s:call('fsharp/project', s:ProjectParms(a:projectPath)) +endfunction +function! s:fsdn(signature) + return s:call('fsharp/fsdn', s:FsdnRequest(a:signature)) +endfunction +function! s:f1Help(filePath, line, character) + return s:call('fsharp/f1Help', s:TextDocumentPositionParams(a:filePath, a:line, a:character)) +endfunction +function! fsharp#documentation(filePath, line, character) + return s:call('fsharp/documentation', s:TextDocumentPositionParams(a:filePath, a:line, a:character)) +endfunction +function! s:documentationSymbol(xmlSig, assembly) + return s:call('fsharp/documentationSymbol', s:DocumentationForSymbolRequest(a:xmlSig, a:assembly)) +endfunction + +function! s:findWorkspace(dir) + let result = s:workspacePeek(a:dir, 2, []) + let content = json_decode(result.result.content) + if len(content.Data.Found) < 1 + return [] + endif + let workspace = { 'Type': 'none' } + for found in content.Data.Found + if workspace.Type == 'none' + let workspace = found + elseif found.Type == 'solution' + if workspace.Type == 'project' then + let workspace = found + else + let curLen = len(workspace.Data.Items) + let newLen = len(found.Data.Items) + if newLen > curLen then + let workspace = found + endif + endif + endif + endfor + if workspace.Type == 'solution' + return [workspace.Data.Path] + else + return workspace.Data.Fsprojs + endif +endfunction + +let s:workspace = [] + +function! s:load(arg) + call s:workspaceLoad(a:arg) + echo "[FSAC] Workspace loaded: " . join(a:arg, ', ') + let s:workspace = s:workspace + a:arg +endfunction + +function! fsharp#loadProject(...) + let prjs = [] + for proj in a:000 + call add(prjs, fnamemodify(proj, ':p')) + endfor + call s:load(prjs) +endfunction + +function! fsharp#loadWorkspaceAuto() + if &ft == 'fsharp' + echom "[FSAC] Loading workspace..." + let bufferDirectory = fnamemodify(resolve(expand('%:p')), ':h') + call s:load(s:findWorkspace(bufferDirectory)) + endif +endfunction + +function! fsharp#reloadProjects() + if len(s:workspace) > 0 + call s:workspaceLoad(s:workspace) + echom "[FSAC] Workspace reloaded." + else + echom "[FSAC] Workspace is empty" + endif +endfunction + +function! fsharp#OnFSProjSave() + if g:fsharp#automatic_reload_workspace + call fsharp#reloadProjects() + endif +endfunction + +function! fsharp#showSignature() + let result = s:signature(expand('%:p'), line('.') - 1, col('.') - 1) + if exists('result.result.content') + let content = json_decode(result.result.content) + if exists('content.Data') + echom substitute(content.Data, '\n\+$', ' ', 'g') + endif + endif +endfunction + +function! fsharp#OnCursorMove() + if g:fsharp#show_signature_on_cursor_move + call fsharp#showSignature() + endif +endfunction + +let s:script_root_dir = expand(':p:h') . "/../" +let s:fsac = fnamemodify(s:script_root_dir . "fsac/fsautocomplete.dll", ":p") +let g:fsharp#languageserver_command = + \ ['dotnet', s:fsac, + \ '--background-service-enabled', + \ '--mode', 'lsp' + \ ] + +function! s:download() + echom "Downloading FSAC" + let zip = s:script_root_dir . "fsac.zip" + call system( + \ 'curl -fLo ' . zip . ' --create-dirs ' . + \ '"https://ci.appveyor.com/api/projects/fsautocomplete/fsautocomplete/artifacts/bin/pkgs/fsautocomplete.netcore.zip?branch=master"' + \ ) + call system('unzip -d ' . s:script_root_dir . "/fsac " . zip) + echom "FSAC Downloaded" +endfunction + +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim: sw=4 et sts=4 diff --git a/autoload/fsharpbinding/python.vim b/autoload/fsharpbinding/python.vim deleted file mode 100644 index 6bfbb1f..0000000 --- a/autoload/fsharpbinding/python.vim +++ /dev/null @@ -1,489 +0,0 @@ -" Vim autoload functions - -if exists('g:loaded_autoload_fsharpbinding_python') - finish -endif -let g:loaded_autoload_fsharpbinding_python = 1 - -let s:cpo_save = &cpo -set cpo&vim - -if has('python3') - let s:py_env = 'python3 << EOF' -else - let s:py_env = 'python << EOF' -endif - -" taken from: http://stackoverflow.com/questions/1533565/how-to-get-visually-selected-text-in-vimscript -function! s:get_visual_selection() - " Why is this not a built-in Vim script function?! - let [lnum1, col1] = getpos("'<")[1:2] - let [lnum2, col2] = getpos("'>")[1:2] - let lines = getline(lnum1, lnum2) - let lines[-1] = lines[-1][: col2 - (&selection == 'inclusive' ? 1 : 2)] - let lines[0] = lines[0][col1 - 1:] - "return join(lines, "\n") - return lines -endfunction - -function! s:get_complete_buffer() - return join(getline(1, '$'), "\n") -endfunction - -if has('win32') || has('win32unix') - function! s:platform_exec(cmd) - execute '!' . a:cmd - endfunction -else - function! s:platform_exec(cmd) - execute '!mono' a:cmd - endfunction -endif - -" Vim73-compatible version of pyeval -" taken from: http://stackoverflow.com/questions/13219111/how-to-embed-python-expression-into-s-command-in-vim -function s:pyeval(expr) - if version > 703 - if has('python3') - return py3eval(a:expr) - elseif has('python') - return pyeval(a:expr) - endif - endif -exe s:py_env -import json -arg = vim.eval('a:expr') -result = json.dumps(eval(arg)) -vim.command('return ' + result) -EOF -endfunction - -function! fsharpbinding#python#LoadLogFile() -exe s:py_env -print(G.fsac.logfiledir) -EOF -endfunction - -function! fsharpbinding#python#ParseProject(...) - execute 'wa' - if a:0 > 0 - exe s:py_env -G.fsac.project(vim.eval("a:1")) -EOF - elseif exists('b:proj_file') - exe s:py_env -G.fsac.project(vim.eval("b:proj_file")) -EOF - endif -endfunction - -function! fsharpbinding#python#BuildProject(...) - try - execute 'wa' - if a:0 > 0 - execute '!' . shellescape(g:fsharp_xbuild_path) . ' ' . fnameescape(a:1) - elseif exists('b:proj_file') - execute '!' . shellescape(g:fsharp_xbuild_path) . ' ' . fnameescape(b:proj_file) "/verbosity:quiet /nologo" - call fsharpbinding#python#ParseProject() - let b:fsharp_buffer_changed = 1 - else - echoe "no project file could be found" - endif - catch - echoe "failed to execute build. ex: " v:exception - endtry -endfunction - -function! fsharpbinding#python#RunProject(...) - try - execute 'wa' - if a:0 > 0 - call s:platform_exec(fnameescape(a:1)) - elseif exists('b:proj_file') - let cmd = 'G.projects[vim.eval("b:proj_file")]["Output"]' - "echom "runproj pre s:pyeval " cmd - let target = s:pyeval(cmd) - call s:platform_exec(fnameescape(target)) - else - echoe "no project file could be found" > 0 - endif - catch - echoe "failed to execute build. ex: " v:exception - endtry -endfunction - -function! fsharpbinding#python#RunTests(...) - try - execute 'wa' - call fsharpbinding#python#BuildProject() - if a:0 > 0 && exists('g:fsharp_test_runner') - call s:platform_exec(shellescape(g:fsharp_test_runner) . ' ' . fnameescape(a:1)) - elseif exists('b:proj_file') && exists('g:fsharp_test_runner') - let cmd = 'G.projects[vim.eval("b:proj_file")]["Output"]' - let target = s:pyeval(cmd) - call s:platform_exec(shellescape(g:fsharp_test_runner) . ' ' . fnameescape(target)) - else - echoe "no project file or test runner could be found" - endif - catch - echoe "failed to execute tests. ex: " v:exception - endtry -endfunction - -" Get type information for the expression at the cursor -" includeComments [0|1] -function! fsharpbinding#python#TypeInfo(includeComments) - exe s:py_env -b = vim.current.buffer -G.fsac.parse(b.name, True, b) -row, col = vim.current.window.cursor -res = G.fsac.tooltip(b.name, row, col + 1, vim.eval('a:includeComments') != '0') -lines = res.splitlines() -first = "" -if len(lines): - first = lines[0] -if first.startswith('Multiple') or first.startswith('type'): - vim.command('echo "%s"' % res) -elif first.startswith('HasComments'): - vim.command('echo "%s"' % res.replace("HasComments", "", 1)) -else: - vim.command('echo "%s"' % first) -EOF - let b:fsharp_buffer_changed = 0 -endfunction - -" Get a type definition for an expression -function! fsharpbinding#python#TypeCheck() - call fsharpbinding#python#TypeInfo(0) -endfunction - -" Get a type definition and available comment block for an expression -function! fsharpbinding#python#TypeHelp() - call fsharpbinding#python#TypeInfo(1) -endfunction - -" probable loclist format -" {'lnum': 2, 'bufnr': 1, 'col': 1, 'valid': 1, 'vcol': 1, 'nr': -1, 'type': 'W', 'pattern': '', 'text': 'Expected an assignment or function call and instead saw an expression.'} - -" G.fsac format -" {"StartLine":4,"StartLine":5,"EndLine":4,"EndLine":5,"StartColumn":0,"EndColumn":4,"Severity":"Error","Message":"The value or constructor 'asdf' is not defined","Subcategory":"typecheck","FileName":"/Users/karlnilsson/code/kjnilsson/fsharp-vim/test.fsx"} -function! fsharpbinding#python#CurrentErrors() - let result = [] - let buf = bufnr('%') - try - if version > 703 - let errs = s:pyeval('G.fsac.errors_current()') - else - " Send a sync parse request if Vim 7.3, otherwise misses response for large files - let errs = s:pyeval("G.fsac.errors(vim.current.buffer.name, True, vim.current.buffer)") - endif - - if !empty(errs) - for e in errs['Errors'] - call add(result, - \{'lnum': e['StartLine'], - \ 'col': e['StartColumn'] - 1, - \ 'type': e['Severity'][0], - \ 'text': e['Message'], - \ 'hl': '\%' . e['StartLine'] . 'l\%>' . (e['StartColumn'] - 1) . 'c\%<' . e['EndColumn'] . 'c', - \ 'bufnr': buf, - \ 'valid': 1 }) - endfor - endif - catch - echoe "failed to parse file. ex: " v:exception - endtry - return result -endfunction - -function! fsharpbinding#python#ToggleHelptext() - if g:fsharp_completion_helptext - let g:fsharp_completion_helptext = 0 - else - let g:fsharp_completion_helptext = 1 - endif -endfunction - -function! fsharpbinding#python#Complete(findstart, base) - let line = getline('.') - let idx = col('.') - 1 "1-indexed - - " if there are trailing characters move one further back - if len(line) >= idx - let idx -= 1 - endif - - while idx > 0 - let c = line[idx] - if c == ' ' || c == '.' || c == '(' - let idx += 1 - break - endif - let idx -= 1 - endwhile - - if a:findstart == 1 - return idx - else - - exe s:py_env -b = vim.current.buffer -row, col = vim.current.window.cursor -line = b[row - 1] -if col > len(line): - col = len(line) -G.fsac.parse(b.name, True, b) -for line in G.fsac.complete(b.name, row, col + 1, vim.eval('a:base')): - name = str(line['Name']) - abbr = name - if " " in name: - name = "``%s``" % name - glyph = str(line['Glyph']) - if int(vim.eval('g:fsharp_completion_helptext')) > 0: - include_comments = vim.eval('g:fsharp_helptext_comments') != '0' - ht = G.fsac.helptext(name, include_comments) - x = {'word': name, - 'abbr': abbr, - 'info': ht, - 'menu': glyph} - vim.eval('complete_add(%s)' % x) - else: - x = {'word': name, - 'menu': glyph} - vim.eval('complete_add(%s)' % x) - -EOF - return [] - endif - let b:fsharp_buffer_changed = 0 -endfunction - -function! fsharpbinding#python#GoBackFromDecl() - exe s:py_env -b = vim.current.buffer -w = vim.current.window -try: - f, cur = G.locations.pop() - # declared within same file - if b.name == f: - w.cursor = cur - else: - pyvim.jump(f, cur) -except: - print("no more locations") -EOF -endfunction - -function! fsharpbinding#python#GotoDecl() - exe s:py_env -b = vim.current.buffer -w = vim.current.window -G.fsac.parse(b.name, True, b) -row, col = vim.current.window.cursor -res = G.fsac.finddecl(b.name, row, col + 1) -G.locations.append((b.name, w.cursor)) -if res == None: - vim.command('echo "declaration not found"') -else: - f, cur = res - # declared within same file - if b.name == f: - w.cursor = cur - else: - pyvim.jump(f, cur) -EOF -endfunction - -function! fsharpbinding#python#OnBufWritePre() - "ensure a parse has been requested before BufWritePost is called - exe s:py_env -G.fsac.parse(vim.current.buffer.name, True, vim.current.buffer) -EOF - let b:fsharp_buffer_changed = 0 -endfunction - -function! fsharpbinding#python#OnInsertLeave() - if exists ("b:fsharp_buffer_changed") != 0 - if b:fsharp_buffer_changed == 1 - exe s:py_env -G.fsac.parse(vim.current.buffer.name, True, vim.current.buffer) -EOF - endif - endif -endfunction - -function! fsharpbinding#python#OnCursorHold() - if exists ("g:fsharp_only_check_errors_on_write") != 0 && - \ exists (':SyntasticCheck') == 2 - if g:fsharp_only_check_errors_on_write != 1 && b:fsharp_buffer_changed == 1 - exec "SyntasticCheck" - endif - endif - let b:fsharp_buffer_changed = 0 -endfunction - -function! fsharpbinding#python#OnTextChanged() - let b:fsharp_buffer_changed = 1 - "TODO: make an parse_async that writes to the server on a background thread - exe s:py_env -G.fsac.parse(vim.current.buffer.name, True, vim.current.buffer) -EOF -endfunction - -function! fsharpbinding#python#OnTextChangedI() - let b:fsharp_buffer_changed = 1 -endfunction - -" Ensure that python processes close on exit -function! fsharpbinding#python#OnVimLeave() -exe s:py_env -G.fsac.shutdown() -G.fsi.shutdown() -EOF -endfunction - -function! fsharpbinding#python#OnBufEnter() - let b:fsharp_buffer_changed = 1 - set updatetime=500 -exe s:py_env -G.fsac.parse(vim.current.buffer.name, True, vim.current.buffer) - -file_dir = vim.eval("expand('%:p:h')") -G.fsi.cd(file_dir) -if vim.eval("exists('b:proj_file')") == 1: - G.fsac.project(vim.eval("b:proj_file")) -EOF - "set makeprg - if !filereadable(expand("%:p:h")."/Makefile") - if exists('b:proj_file') - let &l:makeprg=shellescape(g:fsharp_xbuild_path) . ' ' . b:proj_file . ' /verbosity:quiet /nologo /p:Configuration=Debug' - setlocal errorformat=\ %#%f(%l\\\,%c):\ %m - endif - endif -endfunction - -function! fsharpbinding#python#FsiReset(fsi_path) - exe s:py_env -G.fsi.shutdown() -G.fsi = FSharpInteractive(vim.eval('a:fsi_path')) -G.fsi.cd(vim.eval("expand('%:p:h')")) -EOF - exec 'bw fsi-out' - echo "fsi reset" -endfunction - -function! fsharpbinding#python#FsiInput() - let text = input('> ') - call fsharpbinding#python#FsiEval(text) -endfunction - -function! fsharpbinding#python#FsiSend(text) - exe s:py_env -path = vim.current.buffer.name -(row, col) = vim.current.window.cursor -G.fsi.set_loc(path, row) -G.fsi.send(vim.eval('a:text')) -EOF -endfunction - -function! fsharpbinding#python#FsiShow() - try - if bufnr('fsi-out') == -1 - exec 'badd fsi-out' - else - exec 'vsplit fsi-out' - setlocal buftype=nofile - setlocal bufhidden=hide - setlocal noswapfile - exec 'wincmd p' - endif - catch - echoe "failed to display fsi output. ex" v:exception - endtry -endfunction - -function! fsharpbinding#python#FsiPurge() -exe s:py_env -lines = G.fsi.purge() -for b in vim.buffers: - if 'fsi-out' in b.name: - b.append(lines) - break -EOF -endfunction - -function! fsharpbinding#python#FsiClear() -exe s:py_env -lines = G.fsi.purge() -for b in vim.buffers: - if 'fsi-out' in b.name: - del b[:] - break -EOF -endfunction -function! fsharpbinding#python#FsiRead(time_out) -exe s:py_env -lines = G.fsi.read_until_prompt(float(vim.eval('a:time_out'))) -for b in vim.buffers: - if 'fsi-out' in b.name: - b.append(lines) - for w in vim.current.tabpage.windows: - if b.name in w.buffer.name: - w.cursor = len(b) - 1, 0 - vim.command('exe %s"wincmd w"' % w.number) - vim.command('exe "normal! G"') - vim.command('exe "wincmd p"') - break - break -#echo first nonempty line -for l in lines: - if l != "": - vim.command("redraw | echo '%s'" % l.replace("'", "''")) - break -EOF -endfunction - -function! fsharpbinding#python#FsiEval(text) - try - "clear anything in the buffer - call fsharpbinding#python#FsiPurge() - call fsharpbinding#python#FsiSend(a:text) - if bufnr('fsi-out') == -1 - exec 'badd fsi-out' - - " auto-open the fsi-out buffer - if exists('g:fsharp_fsi_show_auto_open') - if g:fsharp_fsi_show_auto_open == 1 - call fsharpbinding#python#FsiShow() - endif - endif - endif - call fsharpbinding#python#FsiRead(5) - catch - echoe "fsi eval failure. ex" v:exception - endtry -endfunction - -function! fsharpbinding#python#FsiSendLine() - let text = getline('.') - "need to do this before FsiEval else we'll force a refresh - exec 'normal j' - call fsharpbinding#python#FsiEval(text) -endfunction - -function! fsharpbinding#python#FsiSendSel() - let lines = s:get_visual_selection() - exec 'normal' len(lines) . 'j' - let text = join(lines, "\n") - call fsharpbinding#python#FsiEval(text) -endfunction - -function! fsharpbinding#python#FsiSendAll() - let text = s:get_complete_buffer() - call fsharpbinding#python#FsiEval(text) -endfunction - -let &cpo = s:cpo_save -unlet s:cpo_save - -" vim: sw=4 et sts=4 diff --git a/ftdetect/fsharp.vim b/ftdetect/fsharp.vim index cd05adf..6c9b1ec 100644 --- a/ftdetect/fsharp.vim +++ b/ftdetect/fsharp.vim @@ -1,2 +1,3 @@ " F#, fsharp autocmd BufNewFile,BufRead *.fs,*.fsi,*.fsx set filetype=fsharp +autocmd BufNewFile,BufRead *.fsproj set filetype=fsharp_project diff --git a/ftplugin/fsharp.vim b/ftplugin/fsharp.vim index c17a94e..43a491e 100644 --- a/ftplugin/fsharp.vim +++ b/ftplugin/fsharp.vim @@ -1,174 +1,32 @@ " Vim filetype plugin -" Language: F# -" Last Change: Sun 25 Jun 2017 -" Maintainer: Gregor Uhlenheuer -if exists('b:did_ftplugin') +if exists('b:did_fsharp_ftplugin') finish endif -let b:did_ftplugin = 1 +let b:did_fsharp_ftplugin = 1 -if has('python3') - let s:py_env = 'python3 << EOF' -else - let s:py_env = 'python << EOF' -endif +let script_dir = expand(':p:h') +let fsac = script_dir . "/../fsac/fsautocomplete.dll" -"set some defaults -if !exists('g:fsharp_only_check_errors_on_write') - let g:fsharp_only_check_errors_on_write = 0 +if empty(glob(fsac)) + echoerr "FSAC not found. :FSharpDownloadFSAC to download." + finish endif -if !exists('g:fsharp_completion_helptext') - let g:fsharp_completion_helptext = 1 + +" set some defaults +if !exists('g:fsharp#automatic_workspace_init') + let g:fsharp#automatic_workspace_init = 1 endif -if !exists('g:fsharp_helptext_comments') - let g:fsharp_helptext_comments= 0 +if !exists('g:fsharp#automatic_reload_workspace') + let g:fsharp#automatic_reload_workspace = 1 endif -" Enable checker by default -if !exists('g:syntastic_fsharp_checkers') - let g:syntastic_fsharp_checkers = ['syntax'] +if !exists('g:fsharp#show_signature_on_cursor_move') + let g:fsharp#show_signature_on_cursor_move = 1 endif let s:cpo_save = &cpo set cpo&vim -" check for python support -if !(has('python3') || has('python')) - echoerr "Python environment not found" - finish -else - exe s:py_env -import vim -import os -import re -fsharp_dir = vim.eval("expand(':p:h')") -file_dir = vim.eval("expand('%:p:h')") -sys.path.append(fsharp_dir) - -from fsharpvim import FSAutoComplete, G -from fsi import FSharpInteractive -import pyvim - -debug = vim.eval("get(g:, 'fsharpbinding_debug', 0)") != '0' -if G.fsac is None: - G.fsac = FSAutoComplete(fsharp_dir, debug) - G.fsac.get_paths() -vim_var_exists = lambda var_name: vim.eval("exists('%s')" % var_name) != '0' -# retrieve path to a compiler tool (fsi, msbuild/xbuild) with fsautocomplete unless set by the user -def get_path(var_name, path_obj): - if vim_var_exists(var_name): - return - if path_obj not in G.paths: - G.paths = G.fsac.get_paths() - if path_obj in G.paths: - vim.command('let %s = "%s"' % (var_name, re.escape(G.paths[path_obj]))) -get_path('g:fsharp_interactive_bin', 'Fsi') -get_path('g:fsharp_xbuild_path', 'MSBuild') -if G.fsi is None and vim_var_exists('g:fsharp_interactive_bin'): - G.fsi = FSharpInteractive(vim.eval('g:fsharp_interactive_bin'), debug) - -#find project file if any - assumes fsproj file will be in the same directory as the fs or fsi file -b = vim.current.buffer -x,ext = os.path.splitext(b.name) -if '.fs' == ext or '.fsi' == ext: - dir = os.path.dirname(os.path.realpath(b.name)) - projs = list(filter(lambda f: f.lower() == 'project.json' or f.lower().endswith('.fsproj'), os.listdir(dir))) - if len(projs): - proj_file = os.path.join(dir, projs[0]) - vim.command("let b:proj_file = '%s'" % proj_file) - G.fsac.project(proj_file) -G.fsac.parse(b.name, True, b) -EOF - if !exists('g:fsharp_map_keys') - let g:fsharp_map_keys = 1 - endif - - if !exists('g:fsharp_map_prefix') - let g:fsharp_map_prefix = '' - endif - - if !exists('g:fsharp_map_typecheck') - let g:fsharp_map_typecheck = 't' - endif - - if !exists('g:fsharp_map_typehelp') - let g:fsharp_map_typehelp = 'h' - endif - - if !exists('g:fsharp_map_gotodecl') - let g:fsharp_map_gotodecl = 'd' - endif - - if !exists('g:fsharp_map_gobackfromdecl') - let g:fsharp_map_gobackfromdecl = 's' - endif - - if !exists('g:fsharp_map_fsiinput') - let g:fsharp_map_fsiinput = 'e' - endif - - if g:fsharp_map_keys - execute "nnoremap " g:fsharp_map_prefix.g:fsharp_map_typecheck ":call fsharpbinding#python#TypeCheck()" - execute "nnoremap " g:fsharp_map_prefix.g:fsharp_map_typehelp ":call fsharpbinding#python#TypeHelp()" - execute "nnoremap " g:fsharp_map_prefix.g:fsharp_map_gotodecl ":call fsharpbinding#python#GotoDecl()" - execute "nnoremap " g:fsharp_map_prefix.g:fsharp_map_gobackfromdecl ":call fsharpbinding#python#GoBackFromDecl()" - execute "nnoremap " g:fsharp_map_prefix.g:fsharp_map_fsiinput ":call fsharpbinding#python#FsiInput()" - endif - - com! -buffer FSharpLogFile call fsharpbinding#python#LoadLogFile() - com! -buffer FSharpToggleHelptext call fsharpbinding#python#ToggleHelptext() - com! -buffer -nargs=* -complete=file FSharpParseProject call fsharpbinding#python#ParseProject() - com! -buffer -nargs=* -complete=file FSharpBuildProject call fsharpbinding#python#BuildProject() - com! -buffer -nargs=* -complete=file FSharpRunTests call fsharpbinding#python#RunTests() - com! -buffer -nargs=* -complete=file FSharpRunProject call fsharpbinding#python#RunProject() - - "fsi - com! -buffer FsiShow call fsharpbinding#python#FsiShow() - com! -buffer FsiClear call fsharpbinding#python#FsiClear() - com! -buffer FsiRead call fsharpbinding#python#FsiRead(0.5) "short timeout as there may not be anything to read - com! -buffer FsiReset call fsharpbinding#python#FsiReset(g:fsharp_interactive_bin) - com! -buffer -nargs=1 FsiEval call fsharpbinding#python#FsiEval() - com! -buffer FsiEvalBuffer call fsharpbinding#python#FsiSendAll() - - if !exists('g:fsharp_map_fsisendline') - let g:fsharp_map_fsisendline = 'i' - endif - - if !exists('g:fsharp_map_fsisendsel') - let g:fsharp_map_fsisendsel = 'i' - endif - - if g:fsharp_map_keys - nnoremap  :call fsharpbinding#python#FsiSendLine() - vnoremap  :call fsharpbinding#python#FsiSendSel() - - execute "nnoremap " g:fsharp_map_prefix.g:fsharp_map_fsisendline ":call fsharpbinding#python#FsiSendLine()" - execute "vnoremap " g:fsharp_map_prefix.g:fsharp_map_fsisendsel ":call fsharpbinding#python#FsiSendSel()" - endif - - augroup fsharpbindings_au - au! - " closing the scratch window after leaving insert mode - " is common practice - au BufWritePre *.fs,*.fsi,*fsx call fsharpbinding#python#OnBufWritePre() - if version > 703 - " these events new in Vim 7.4 - au TextChanged *.fs,*.fsi,*fsx call fsharpbinding#python#OnTextChanged() - au TextChangedI *.fs,*.fsi,*fsx call fsharpbinding#python#OnTextChangedI() - au CursorHold *.fs,*.fsi,*fsx call fsharpbinding#python#OnCursorHold() - au InsertLeave *.fs,*.fsi,*fsx call fsharpbinding#python#OnInsertLeave() - endif - au BufEnter *.fs,*.fsi,*fsx call fsharpbinding#python#OnBufEnter() - au InsertLeave *.fs,*.fsi,*fsx if pumvisible() == 0|silent! pclose|endif - augroup END - - " Python process cleanup - autocmd VimLeavePre * call fsharpbinding#python#OnVimLeave() - - " omnicomplete - setlocal omnifunc=fsharpbinding#python#Complete -endif - " enable syntax based folding setl fdm=syntax @@ -180,6 +38,21 @@ setl comments=s0:*\ -,m0:*\ \ ,ex0:*),s1:(*,mb:*,ex:*),:\/\/\/,:\/\/ " make ftplugin undo-able let b:undo_ftplugin = 'setl fo< cms< com< fdm<' +if g:fsharp#automatic_workspace_init + augroup LanguageClient_config + autocmd! + autocmd User LanguageClientStarted call fsharp#loadWorkspaceAuto() + augroup END +endif + +augroup FSharpLC + autocmd! CursorMoved call fsharp#OnCursorMove() +augroup END + +com! -buffer FSharpLoadWorkspaceAuto call fsharp#loadWorkspaceAuto() +com! -buffer FSharpReloadWorkspace call fsharp#reloadProjects() +com! -buffer -nargs=* -complete=file FSharpParseProject call fsharp#loadProject() + let &cpo = s:cpo_save " vim: sw=4 et sts=4 diff --git a/ftplugin/fsharp_project.vim b/ftplugin/fsharp_project.vim new file mode 100644 index 0000000..8dcacc6 --- /dev/null +++ b/ftplugin/fsharp_project.vim @@ -0,0 +1,18 @@ + +" Vim filetype plugin + +if exists('b:did_fsharp_project_ftplugin') + finish +endif +let b:did_fsharp_project_ftplugin = 1 + +let s:cpo_save = &cpo +set cpo&vim + +augroup FSharpLC + autocmd! BufWritePost call fsharp#reloadProjects() +augroup END + +let &cpo = s:cpo_save + +" vim: sw=4 et sts=4 diff --git a/ftplugin/fsharpvim.py b/ftplugin/fsharpvim.py deleted file mode 100644 index db0335a..0000000 --- a/ftplugin/fsharpvim.py +++ /dev/null @@ -1,301 +0,0 @@ -from subprocess import Popen, PIPE -from os import path -import sys -import string -import tempfile -import unittest -import json -import threading -import hidewin -import vim - - -class G: - fsac = None - fsi = None - paths = {} - locations = [] - projects = {} - - -class Interaction: - def __init__(self, proc, timeOut, logfile = None): - self.data = None - self.event = threading.Event() - self.proc = proc - self._timeOut = timeOut - self.logfile = logfile - self.debug = not logfile is None - - def _write(self, txt): - self.proc.stdin.write(txt) - self.proc.stdin.flush() - - if self.debug: - self.logfile.write("> " + txt) - self.logfile.flush() - - def read(self): - self.event.wait(self._timeOut) - if self.debug: - self.logfile.write('msg received %s\n' % self.data) - return self.data - - def send(self, command): - self.data = None - self.event.clear() - self._write(command) - return self.read() - - def send_async(self, command): - self.data = None - self.event.clear() - self._write(command) - - # only on worker thread - def update(self, data): - self.data = data - self.event.set() - - -class FSAutoComplete: - def __init__(self, dir, debug = False): - if debug: - self.logfiledir = tempfile.gettempdir() + "/log.txt" - self.logfile = open(self.logfiledir, "w") - else: - self.logfile = None - - command = ['mono', dir + '/bin/fsautocomplete.exe'] - opts = { 'stdin': PIPE, 'stdout': PIPE, 'stderr': PIPE, 'universal_newlines': True } - hidewin.addopt(opts) - try: - self.p = Popen(command, **opts) - except OSError: - try: - self.p = Popen(command[1:], **opts) - except OSError as error: - msg = "Error encountered while trying to execute %s: %s" % (command[1:], error) - raise OSError(msg) - - self.debug = debug - self.switch_to_json() - - self.completion = Interaction(self.p, 3, self.logfile) - self._finddecl = Interaction(self.p, 1, self.logfile) - self._tooltip = Interaction(self.p, 1, self.logfile) - self._helptext = Interaction(self.p, 1, self.logfile) - self._errors = Interaction(self.p, 3, self.logfile) - self._project = Interaction(self.p, 3, self.logfile) - self._getpaths = Interaction(self.p, 1, self.logfile) - - self.worker = threading.Thread(target=self.work, args=(self,)) - self.worker.daemon = True - self.worker.start() - - def __log(self, msg): - if self.debug: - self.logfile.write(msg) - self.logfile.flush() - - def send(self, txt): - if self.debug: - self.logfile.write("> " + txt) - self.logfile.flush() - - self.p.stdin.write(txt) - - def work(self,_): - if self.debug: - self.logfile2 = open(tempfile.gettempdir() + "/log2.txt", "w") - - while True: - data = self.p.stdout.readline() - - if self.debug: - self.logfile2.write("::work read: %s" % data) - self.logfile2.flush() - - # Temporary fix for unwanted stdout messages from Mono V5.0.1.1 - # To be removed for the next Mono release - if (len(data) == 0 or data[0] != '{'): - continue - - parsed = json.loads(data) - if parsed['Kind'] == "completion": - self.completion.update(parsed['Data']) - elif parsed['Kind'] == "tooltip": - self._tooltip.update(parsed['Data']) - elif parsed['Kind'] == "helptext": - self._helptext.update(parsed['Data']) - elif parsed['Kind'] == "errors": - self._errors.update(parsed['Data']) - elif parsed['Kind'] == "project": - data = parsed['Data'] - G.projects[data['Project']] = data - self._project.update(data) - elif parsed['Kind'] == "finddecl": - self._finddecl.update(parsed['Data']) - elif parsed['Kind'] == "compilerlocation": - self._getpaths.update(parsed['Data']) - - def help(self): - self.send("help\n") - - def switch_to_json(self): - self.send("outputmode json\n") - - def project(self, fn): - self.send("project \"%s\" verbose\n" % path.abspath(fn)) - - def parse(self, fn, full, lines): - self.send("parse \"%s\"\n" % (fn)) - for line in lines: - self.send(line + "\n") - self.send("<>\n") - - def quit(self): - self.send("quit\n") - self.p.wait() - - if self.debug: - self.logfile.close() - - def get_paths(self): - return self._getpaths.send("compilerlocation\n") - - def complete(self, fn, line, column, base): - self.__log('complete: base = %s\n' % base) - - msg = self.completion.send('completion "%s" \"%s\" %d %d filter=%s\n' % (fn, self._current_line(), line, column, base)) - - self.__log('msg received %s\n' % msg) - - if msg is None: - return [] - - if base != '': - msg = list(filter(lambda line: line['Name'].lower().find(base.lower()) != -1, - msg)) - msg.sort(key=lambda x: x['Name'].startswith(base), reverse=True) - - return msg - - def finddecl(self, fn, line, column): - msg = self._finddecl.send('finddecl "%s" \"%s\" %d %d\n' % (fn, self._current_line(), line, column)) - if msg != None: - return str(msg['File']), (int(str(msg['Line'])), int(str(msg['Column']))) - else: - return None - - def errors(self, fn, full, lines): - self.__log('errors: fn = %s\n' % fn) - - fulltext = "full" if full else "" - self.send("parse \"%s\" %s\n" % (fn, fulltext)) - - for line in lines: - self.send(line + "\n") - - msg = self._errors.send("<>\n") - self.__log('msg received: %s\n' % msg) - - return msg - - def _current_line(self): - return vim.current.line.replace("\"", "\\\"") - - def errors_current(self): - msg = self._errors.read() - if msg == None: - return [] - else: - return msg - - def _vim_encode(self, s): - """Encode string so vim can properly display it""" - if (sys.version_info > (3, 0)): - return s - return s.encode(vim.eval("&encoding")) - - def _format_comment(self, comment): - """Clean comment so it displays nicely""" - return self._vim_encode(comment.replace("\"", "\\\"").replace("\n ", "\n").strip()) - - def tooltip(self, fn, line, column, include_comments): - """Get the tooltip information for an expression""" - msg = self._tooltip.send('tooltip "%s" \"%s\" %d %d 500\n' % (fn, self._current_line(), line, column)) - if msg == None: - return "" - - output_signature = "" - for ols in msg: - for ol in ols: - output_signature = output_signature + self._vim_encode(ol['Signature']) + "\n" - - output_comments = "" - if include_comments: - for ols in msg: - for ol in ols: - output_comments = output_comments + self._format_comment(ol['Comment']) + "\n" - - if include_comments and output_comments.strip() != "": - output = 'HasComments%s\n%s' % (output_signature, output_comments) - else: - output = output_signature - - return output - - def helptext(self, candidate, include_comments): - """Get the helptext for an expression (used for omni completion)""" - msg = self._helptext.send('helptext %s\n' % candidate) - if msg == None: - return "" - - output_signature = "" - for ols in msg['Overloads']: - for ol in ols: - output_signature = output_signature + self._vim_encode(ol['Signature']) + "\n" - - output_comments = "" - if include_comments: - for ols in msg['Overloads']: - for ol in ols: - output_comments = output_comments + self._format_comment(ol['Comment']) + "\n" - - if include_comments and output_comments.strip() != "": - msg = '%s\n%s' % (output_signature, output_comments) - else: - msg = output_signature - - if "\'" in msg and "\\\"" in msg: - msg = msg.replace("\\\"", "\'") #HACK: dictionary parsing in vim gets weird if both ' and " get printed in the same string, so replace " with ' - elif "\n" in msg: - msg = msg + "\n\n'" #HACK: - the ' is inserted to ensure that newlines are interpreted properly in the preview window - return msg - - def shutdown(self): - """Shutdown fsautocomplete process""" - try: - self.send("quit\n") - self.p.kill() - except: - pass - -class FSharpVimFixture(unittest.TestCase): - def setUp(self): - self.fsac = FSAutoComplete('.') - self.testscript = 'test/TestScript.fsx' - with open(self.testscript, 'r') as content_file: - content = map(lambda line: line.strip('\n'), list(content_file)) - - self.fsac.parse(self.testscript, True, content) - - def tearDown(self): - self.fsac.quit() - - def test_completion(self): - completions = self.fsac.complete(self.testscript, 8, 16, '') - -if __name__ == '__main__': - unittest.main() diff --git a/ftplugin/fsi.py b/ftplugin/fsi.py deleted file mode 100644 index 05db717..0000000 --- a/ftplugin/fsi.py +++ /dev/null @@ -1,122 +0,0 @@ -from subprocess import Popen, PIPE -from os import path -import string -import threading -import tempfile -import uuid -import hidewin - -try: - from queue import Queue -except: - from Queue import Queue - - -class FSharpInteractive: - def __init__(self, fsi_path, is_debug = False): - self._debug = is_debug - #self.logfiledir = tempfile.gettempdir() + "/log.txt" - #self.logfile = open(self.logfiledir, "w") - id = 'vim-' + str(uuid.uuid4()) - command = [fsi_path, '--fsi-server:%s' % id, '--nologo'] - opts = { 'stdin': PIPE, 'stdout': PIPE, 'stderr': PIPE, 'shell': False, 'universal_newlines': True } - hidewin.addopt(opts) - - try: - self.p = Popen(command, **opts) - except Exception as e: - raise Exception ('Error executing fsi. g:fsharp_interactive_bin="' + fsi_path + '" ' + str(e)) - - if is_debug: - logfiledir = tempfile.gettempdir() + "/fsi-log.txt" - self.logfile = open(logfiledir, "w") - - self._should_work = True - self.lines = Queue() - self.worker = threading.Thread(target=self._work, args=[]) - self.worker.daemon = True - self.worker.start() - self.err_worker = threading.Thread(target=self._err_work, args=[]) - self.err_worker.daemon = True - self.err_worker.start() - x = self.purge() - self._current_path = None - - def _log(self, msg): - if self._debug: - self.logfile.write(msg + "\n") - self.logfile.flush() - - def shutdown(self): - """Shutdown fsi process""" - print("shutting down fsi") - self._should_work = False - try: - self.p.kill() - except: - pass - - def set_loc(self, path, line_num): - self.p.stdin.write("#" + str(line_num) + " @\"" + path + "\"\n") - self.p.stdin.flush() - - def send(self, txt): - self.p.stdin.write(txt + "\n") - self.p.stdin.write(";;\n") - self.p.stdin.flush() - self._log(">" + txt + ";;") - - def cd(self, path): - if self._current_path == path: - return - self.p.stdin.write("System.IO.Directory.SetCurrentDirectory(@\"" + path + "\");;\n") - self.p.stdin.write("#silentCd @\"" + path + "\";;\n") - self.p.stdin.flush() - self.purge() - self._current_path = path - - def purge(self): - items = [] - while(True): - try: - l = self.lines.get(False).rstrip() - if 'SERVER-PROMPT>' not in l: - items.append(l) - except: - break - return items - - def read_until_prompt(self, time_out): - output = [] - try: - l = self.lines.get(True, time_out) - if 'SERVER-PROMPT>' in l: - return output - output.append(str(l).rstrip()) - while(True): - l = self.read_one() - if 'SERVER-PROMPT>' in l: - return output - output.append(str(l).rstrip()) - return output - except Exception as ex: - output.append(".....") #indicate that there may be more lines of output - return output - - def read_one(self): - return self.lines.get(True, 0.5) - - def _work(self): - while(self._should_work): - try: - l = self.p.stdout.readline() - self.lines.put(l, True) - self._log(l) - except Exception as ex: - print(ex) - - def _err_work(self): - while(self._should_work): - l = self.p.stderr.readline() - self.lines.put(l, True) - self._log( "err: " + l) diff --git a/ftplugin/hidewin.py b/ftplugin/hidewin.py deleted file mode 100644 index 587ae15..0000000 --- a/ftplugin/hidewin.py +++ /dev/null @@ -1,9 +0,0 @@ -import subprocess - -def addopt(opts): - '''add option to hide the window; Windows only''' - if getattr(subprocess, 'STARTUPINFO', 0) != 0: - si = subprocess.STARTUPINFO() - si.dwFlags = 1 # STARTF_USESHOWWINDOW - si.dwShowWindow = 0 # SW_HIDE - opts['startupinfo'] = si diff --git a/ftplugin/pyvim.py b/ftplugin/pyvim.py deleted file mode 100644 index 528d2b0..0000000 --- a/ftplugin/pyvim.py +++ /dev/null @@ -1,5 +0,0 @@ -import vim - -def jump(f, cur): - vim.command(':edit ' + f) - vim.current.window.cursor = cur diff --git a/install.fsx b/install.fsx index 489011b..37fc462 100644 --- a/install.fsx +++ b/install.fsx @@ -11,24 +11,24 @@ let homeVimPath = Environment.GetEnvironmentVariable("HOME") @@ ".vim" else Environment.ExpandEnvironmentVariables("%HOMEDRIVE%%HOMEPATH%") @@ "vimfiles" -let vimInstallDir = homeVimPath @@ "bundle/fsharpbinding-vim" +let vimInstallDir = homeVimPath @@ "bundle/vim_fsharp_languageclient" -let vimBinDir = __SOURCE_DIRECTORY__ @@ "ftplugin/bin" +let vimBinDir = __SOURCE_DIRECTORY__ @@ "fsac" let ftpluginDir = __SOURCE_DIRECTORY__ @@ "ftplugin" let autoloadDir = __SOURCE_DIRECTORY__ @@ "autoload" let syntaxDir = __SOURCE_DIRECTORY__ @@ "syntax" +let indentDir = __SOURCE_DIRECTORY__ @@ "indent" let ftdetectDir = __SOURCE_DIRECTORY__ @@ "ftdetect" -let syntaxCheckersDir = __SOURCE_DIRECTORY__ @@ "syntax_checkers" -let acArchive = "fsautocomplete.zip" -let acVersion = "0.34.0" +let acArchive = "fsautocomplete.netcore.zip" +let acVersion = "master" Target "FSharp.AutoComplete" (fun _ -> CreateDir vimBinDir use client = new WebClient() Net.ServicePointManager.SecurityProtocol <- Net.SecurityProtocolType.Tls12 - tracefn "Downloading version %s of FSharp.AutoComplete" acVersion - client.DownloadFile(sprintf "https://github.com/fsharp/FSharp.AutoComplete/releases/download/%s/%s" acVersion acArchive, vimBinDir @@ acArchive) + tracefn "Downloading version %s of FsAutoComplete" acVersion + client.DownloadFile(sprintf "https://ci.appveyor.com/api/projects/fsautocomplete/fsautocomplete/artifacts/bin/pkgs/%s?branch=%s" acArchive acVersion, vimBinDir @@ acArchive) tracefn "Download complete" tracefn "Unzipping" Unzip vimBinDir (vimBinDir @@ acArchive)) @@ -36,10 +36,11 @@ Target "FSharp.AutoComplete" (fun _ -> Target "Install" (fun _ -> DeleteDir vimInstallDir CreateDir vimInstallDir + CopyDir (vimInstallDir @@ "fsac") vimBinDir (fun _ -> true) CopyDir (vimInstallDir @@ "ftplugin") ftpluginDir (fun _ -> true) CopyDir (vimInstallDir @@ "autoload") autoloadDir (fun _ -> true) CopyDir (vimInstallDir @@ "syntax") syntaxDir (fun _ -> true) - CopyDir (vimInstallDir @@ "syntax_checkers") syntaxCheckersDir (fun _ -> true) + CopyDir (vimInstallDir @@ "indent") indentDir (fun _ -> true) CopyDir (vimInstallDir @@ "ftdetect") ftdetectDir (fun _ -> true)) Target "Clean" (fun _ -> diff --git a/syntax_checkers/fsharp/syntax.vim b/syntax_checkers/fsharp/syntax.vim deleted file mode 100644 index 11a097d..0000000 --- a/syntax_checkers/fsharp/syntax.vim +++ /dev/null @@ -1,24 +0,0 @@ -if exists('g:loaded_syntastic_fsharp_syntax_checker') - finish -endif -let g:loaded_syntastic_fsharp_syntax_checker = 1 - -let s:save_cpo = &cpo -set cpo&vim - -function! SyntaxCheckers_fsharp_syntax_IsAvailable() dict - return has('python3') || has('python') -endfunction - -function! SyntaxCheckers_fsharp_syntax_GetLocList() dict - return fsharpbinding#python#CurrentErrors() -endfunction - -call g:SyntasticRegistry.CreateAndRegisterChecker({ - \ 'filetype': 'fsharp', - \ 'name': 'syntax'}) - -let &cpo = s:save_cpo -unlet s:save_cpo - -" vim: set et sts=4 sw=4: diff --git a/test.fsx b/test.fsx deleted file mode 100644 index 27a468b..0000000 --- a/test.fsx +++ /dev/null @@ -1,20 +0,0 @@ -let (|Item|_|) = Map.tryFind - -let m = Map ["key1", 1; "key2", 2] - -let withAp m k = - match m with - | Item k v -> Some v - | _ -> None - -let without m k = - match Map.tryFind k m with - | Some v -> - Some v - | None -> - None - - - - -