summaryrefslogtreecommitdiffstats
path: root/autoload/fsharp.vim
diff options
context:
space:
mode:
Diffstat (limited to 'autoload/fsharp.vim')
-rw-r--r--autoload/fsharp.vim517
1 files changed, 517 insertions, 0 deletions
diff --git a/autoload/fsharp.vim b/autoload/fsharp.vim
new file mode 100644
index 00000000..6816d822
--- /dev/null
+++ b/autoload/fsharp.vim
@@ -0,0 +1,517 @@
+if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'fsharp') == -1
+
+" 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:prompt(msg)
+ let height = &cmdheight
+ if height < 2
+ set cmdheight=2
+ endif
+ echom a:msg
+ let &cmdheight = height
+endfunction
+
+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, cont)
+ call LanguageClient#Call(a:method, a:params, a:cont)
+endfunction
+
+function! s:signature(filePath, line, character, cont)
+ return s:call('fsharp/signature', s:TextDocumentPositionParams(a:filePath, a:line, a:character), a:cont)
+endfunction
+function! s:signatureData(filePath, line, character, cont)
+ return s:call('fsharp/signatureData', s:TextDocumentPositionParams(a:filePath, a:line, a:character), a:cont)
+endfunction
+function! s:lineLens(projectPath, cont)
+ return s:call('fsharp/lineLens', s:ProjectParms(a:projectPath), a:cont)
+endfunction
+function! s:compilerLocation(cont)
+ return s:call('fsharp/compilerLocation', {}, a:cont)
+endfunction
+function! s:compile(projectPath, cont)
+ return s:call('fsharp/compile', s:ProjectParms(a:projectPath), a:cont)
+endfunction
+function! s:workspacePeek(directory, depth, excludedDirs, cont)
+ return s:call('fsharp/workspacePeek', s:WorkspacePeekRequest(a:directory, a:depth, a:excludedDirs), a:cont)
+endfunction
+function! s:workspaceLoad(files, cont)
+ return s:call('fsharp/workspaceLoad', s:WorkspaceLoadParms(a:files), a:cont)
+endfunction
+function! s:project(projectPath, cont)
+ return s:call('fsharp/project', s:ProjectParms(a:projectPath), a:cont)
+endfunction
+function! s:fsdn(signature, cont)
+ return s:call('fsharp/fsdn', s:FsdnRequest(a:signature), a:cont)
+endfunction
+function! s:f1Help(filePath, line, character, cont)
+ return s:call('fsharp/f1Help', s:TextDocumentPositionParams(a:filePath, a:line, a:character), a:cont)
+endfunction
+function! fsharp#documentation(filePath, line, character, cont)
+ return s:call('fsharp/documentation', s:TextDocumentPositionParams(a:filePath, a:line, a:character), a:cont)
+endfunction
+function! s:documentationSymbol(xmlSig, assembly, cont)
+ return s:call('fsharp/documentationSymbol', s:DocumentationForSymbolRequest(a:xmlSig, a:assembly), a:cont)
+endfunction
+
+" FSharpConfigDto from https://github.com/fsharp/FsAutoComplete/blob/master/src/FsAutoComplete/LspHelpers.fs
+"
+" * The following options seems not working with workspace/didChangeConfiguration
+" since the initialization has already completed?
+" 'AutomaticWorkspaceInit',
+" 'WorkspaceModePeekDeepLevel',
+"
+" * Changes made to linter/unused analyzer settings seems not reflected after sending them to FSAC?
+"
+let s:config_keys_camel =
+ \ [
+ \ {'key': 'AutomaticWorkspaceInit', 'default': 1},
+ \ {'key': 'WorkspaceModePeekDeepLevel', 'default': 2},
+ \ {'key': 'ExcludeProjectDirectories', 'default': []},
+ \ {'key': 'keywordsAutocomplete', 'default': 1},
+ \ {'key': 'ExternalAutocomplete', 'default': 0},
+ \ {'key': 'Linter', 'default': 1},
+ \ {'key': 'UnionCaseStubGeneration', 'default': 1},
+ \ {'key': 'UnionCaseStubGenerationBody'},
+ \ {'key': 'RecordStubGeneration', 'default': 1},
+ \ {'key': 'RecordStubGenerationBody'},
+ \ {'key': 'InterfaceStubGeneration', 'default': 1},
+ \ {'key': 'InterfaceStubGenerationObjectIdentifier', 'default': 'this'},
+ \ {'key': 'InterfaceStubGenerationMethodBody'},
+ \ {'key': 'UnusedOpensAnalyzer', 'default': 1},
+ \ {'key': 'UnusedDeclarationsAnalyzer', 'default': 1},
+ \ {'key': 'SimplifyNameAnalyzer', 'default': 0},
+ \ {'key': 'ResolveNamespaces', 'default': 1},
+ \ {'key': 'EnableReferenceCodeLens', 'default': 1},
+ \ {'key': 'EnableAnalyzers', 'default': 0},
+ \ {'key': 'AnalyzersPath'},
+ \ {'key': 'DisableInMemoryProjectReferences', 'default': 0},
+ \ {'key': 'LineLens', 'default': {'enabled': 'replaceCodeLens', 'prefix': '//'}},
+ \ {'key': 'UseSdkScripts', 'default': 1},
+ \ {'key': 'dotNetRoot'},
+ \ {'key': 'fsiExtraParameters', 'default': []},
+ \ ]
+let s:config_keys = []
+
+function! fsharp#toSnakeCase(str)
+ let sn = substitute(a:str, '\(\<\u\l\+\|\l\+\)\(\u\)', '\l\1_\l\2', 'g')
+ if sn == a:str | return tolower(a:str) | endif
+ return sn
+endfunction
+
+function! s:buildConfigKeys()
+ if len(s:config_keys) == 0
+ for key_camel in s:config_keys_camel
+ let key = {}
+ let key.snake = fsharp#toSnakeCase(key_camel.key)
+ let key.camel = key_camel.key
+ if has_key(key_camel, 'default')
+ let key.default = key_camel.default
+ endif
+ call add(s:config_keys, key)
+ endfor
+ endif
+endfunction
+
+function! g:fsharp#getServerConfig()
+ let fsharp = {}
+ call s:buildConfigKeys()
+ for key in s:config_keys
+ if exists('g:fsharp#' . key.snake)
+ let fsharp[key.camel] = g:fsharp#{key.snake}
+ elseif exists('g:fsharp#' . key.camel)
+ let fsharp[key.camel] = g:fsharp#{key.camel}
+ elseif has_key(key, 'default') && g:fsharp#use_recommended_server_config
+ let g:fsharp#{key.snake} = key.default
+ let fsharp[key.camel] = key.default
+ endif
+ endfor
+ return fsharp
+endfunction
+
+function! g:fsharp#updateServerConfig()
+ let fsharp = fsharp#getServerConfig()
+ let settings = {'settings': {'FSharp': fsharp}}
+ call LanguageClient#Notify('workspace/didChangeConfiguration', settings)
+endfunction
+
+function! s:findWorkspace(dir, cont)
+ let s:cont_findWorkspace = a:cont
+ function! s:callback_findWorkspace(result)
+ let result = a:result
+ 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'
+ let workspace = found
+ else
+ let curLen = len(workspace.Data.Items)
+ let newLen = len(found.Data.Items)
+ if newLen > curLen
+ let workspace = found
+ endif
+ endif
+ endif
+ endfor
+ if workspace.Type == 'solution'
+ call s:cont_findWorkspace([workspace.Data.Path])
+ else
+ call s:cont_findWorkspace(workspace.Data.Fsprojs)
+ endif
+ endfunction
+ call s:workspacePeek(a:dir, g:fsharp#workspace_mode_peek_deep_level, g:fsharp#exclude_project_directories, function("s:callback_findWorkspace"))
+endfunction
+
+let s:workspace = []
+
+function! s:load(arg)
+ let s:loading_workspace = a:arg
+ function! s:callback_load(_)
+ echo "[FSAC] Workspace loaded: " . join(s:loading_workspace, ', ')
+ let s:workspace = s:workspace + s:loading_workspace
+ endfunction
+ call s:workspaceLoad(a:arg, function("s:callback_load"))
+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'
+ call fsharp#updateServerConfig()
+ if g:fsharp#automatic_workspace_init
+ echom "[FSAC] Loading workspace..."
+ let bufferDirectory = fnamemodify(resolve(expand('%:p')), ':h')
+ call s:findWorkspace(bufferDirectory, function("s:load"))
+ endif
+ endif
+endfunction
+
+function! fsharp#reloadProjects()
+ if len(s:workspace) > 0
+ function! s:callback_reloadProjects(_)
+ call s:prompt("[FSAC] Workspace reloaded.")
+ endfunction
+ call s:workspaceLoad(s:workspace, function("s:callback_reloadProjects"))
+ else
+ echom "[FSAC] Workspace is empty"
+ endif
+endfunction
+
+function! fsharp#OnFSProjSave()
+ if &ft == "fsharp_project" && exists('g:fsharp#automatic_reload_workspace') && g:fsharp#automatic_reload_workspace
+ call fsharp#reloadProjects()
+ endif
+endfunction
+
+function! fsharp#showSignature()
+ function! s:callback_showSignature(result)
+ let result = a:result
+ 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
+ call s:signature(expand('%:p'), line('.') - 1, col('.') - 1, function("s:callback_showSignature"))
+endfunction
+
+function! fsharp#OnCursorMove()
+ if g:fsharp#show_signature_on_cursor_move
+ call fsharp#showSignature()
+ endif
+endfunction
+
+function! fsharp#showF1Help()
+ let result = s:f1Help(expand('%:p'), line('.') - 1, col('.') - 1)
+ echo result
+endfunction
+
+function! fsharp#showTooltip()
+ function! s:callback_showTooltip(result)
+ let result = a:result
+ if exists('result.result.content')
+ let content = json_decode(result.result.content)
+ if exists('content.Data')
+ call LanguageClient#textDocument_hover()
+ endif
+ endif
+ endfunction
+ " show hover only if signature exists for the current position
+ call s:signature(expand('%:p'), line('.') - 1, col('.') - 1, function("s:callback_showTooltip"))
+endfunction
+
+let s:script_root_dir = expand('<sfile>: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'
+ \ ]
+
+function! s:download(branch)
+ echom "[FSAC] Downloading FSAC. This may take a while..."
+ 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=' . a:branch . '"'
+ \ )
+ if v:shell_error == 0
+ call system('unzip -o -d ' . s:script_root_dir . "/fsac " . zip)
+ echom "[FSAC] Updated FsAutoComplete to version " . a:branch . ""
+ else
+ echom "[FSAC] Failed to update FsAutoComplete"
+ endif
+endfunction
+
+function! fsharp#updateFSAC(...)
+ if len(a:000) == 0
+ let branch = "master"
+ else
+ let branch = a:000[0]
+ endif
+ call s:download(branch)
+endfunction
+
+let s:fsi_buffer = -1
+let s:fsi_job = -1
+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! s:get_fsi_command()
+ let cmd = g:fsharp#fsi_command
+ for prm in g:fsharp#fsi_extra_parameters
+ let cmd = cmd . " " . prm
+ endfor
+ return cmd
+endfunction
+
+function! fsharp#openFsi(returnFocus)
+ if bufwinid(s:fsi_buffer) <= 0
+ let fsi_command = s:get_fsi_command()
+ " 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(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(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 = -1
+ let s:fsi_job = -1
+ 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
+
+" vim: sw=4 et sts=4
+
+endif